2
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
7
YUI.add('console', function(Y) {
10
* A user interface for viewing log messages.
15
var getCN = Y.ClassNameManager.getClassName,
27
INNER_HTML = 'innerHTML',
29
CONTENT_BOX = 'contentBox',
30
DISABLED = 'disabled',
31
START_TIME = 'startTime',
32
LAST_TIME = 'lastTime',
33
ENTRY_TEMPLATE = 'entryTemplate',
34
RENDERED = 'rendered',
38
C_ENTRY = getCN(CONSOLE,ENTRY),
39
C_PAUSE = getCN(CONSOLE,PAUSE),
40
C_CHECKBOX = getCN(CONSOLE,'checkbox'),
41
C_BUTTON = getCN(CONSOLE,'button'),
42
C_CLEAR = getCN(CONSOLE,CLEAR),
43
C_PAUSE_LABEL = getCN(CONSOLE,PAUSE,'label'),
44
C_ENTRY_META = getCN(CONSOLE,ENTRY,'meta'),
45
C_ENTRY_CAT = getCN(CONSOLE,ENTRY,'cat'),
46
C_ENTRY_SRC = getCN(CONSOLE,ENTRY,'src'),
47
C_ENTRY_TIME = getCN(CONSOLE,ENTRY,'time'),
48
C_ENTRY_CONTENT = getCN(CONSOLE,ENTRY,'content'),
49
C_CONSOLE_HD = getCN(CONSOLE,'hd'),
50
C_CONSOLE_BD = getCN(CONSOLE,'bd'),
51
C_CONSOLE_FT = getCN(CONSOLE,'ft'),
52
C_CONSOLE_CONTROLS = getCN(CONSOLE,'controls'),
53
C_CONSOLE_TITLE = getCN(CONSOLE,TITLE),
55
RE_INLINE_SOURCE = /^(\S+)\s/,
65
isString = L.isString,
66
isNumber = L.isNumber,
67
isObject = L.isObject,
69
substitute = Y.substitute,
70
create = Y.Node.create;
73
* Console creates a visualization for messages logged through calls to a YUI
74
* instance's <code>Y.log( message, category, source )</code> method. The
75
* debug versions of YUI modules will include logging statements to offer some
76
* insight into the steps executed during that module's operation. Including
77
* log statements in your code will cause those messages to also appear in the
78
* Console. Use Console to aid in developing your page or application.
80
* Entry categories are also referred to as the log level, and entries are
81
* filtered against the configured logLevel.
88
Console.superclass.constructor.apply(this,arguments);
94
* The identity of the widget.
96
* @property Console.NAME
103
* Static identifier for logLevel configuration setting to allow all
104
* incoming messages to generate Console entries.
106
* @property Console.LOG_LEVEL_INFO
113
* Static identifier for logLevel configuration setting to allow only
114
* incoming messages of logLevel "warn" or "error"
115
* to generate Console entries.
117
* @property Console.LOG_LEVEL_WARN
124
* Static identifier for logLevel configuration setting to allow only
125
* incoming messages of logLevel "error" to generate
128
* @property Console.LOG_LEVEL_ERROR
135
* Map (object) of classNames used to populate the placeholders in the
136
* Console.ENTRY_TEMPLATE markup when rendering a new Console entry.
138
* <p>By default, the keys contained in the object are:</p>
140
* <li>entry_class</li>
141
* <li>entry_meta_class</li>
142
* <li>entry_cat_class</li>
143
* <li>entry_src_class</li>
144
* <li>entry_time_class</li>
145
* <li>entry_content_class</li>
148
* @property Console.ENTRY_CLASSES
153
entry_class : C_ENTRY,
154
entry_meta_class : C_ENTRY_META,
155
entry_cat_class : C_ENTRY_CAT,
156
entry_src_class : C_ENTRY_SRC,
157
entry_time_class : C_ENTRY_TIME,
158
entry_content_class : C_ENTRY_CONTENT
162
* Map (object) of classNames used to populate the placeholders in the
163
* Console.HEADER_TEMPLATE, Console.BODY_TEMPLATE, and
164
* Console.FOOTER_TEMPLATE markup when rendering the Console UI.
166
* <p>By default, the keys contained in the object are:</p>
168
* <li>console_hd_class</li>
169
* <li>console_bd_class</li>
170
* <li>console_ft_class</li>
171
* <li>console_controls_class</li>
172
* <li>console_checkbox_class</li>
173
* <li>console_pause_class</li>
174
* <li>console_pause_label_class</li>
175
* <li>console_button_class</li>
176
* <li>console_clear_class</li>
177
* <li>console_title_class</li>
180
* @property Console.CHROME_CLASSES
185
console_hd_class : C_CONSOLE_HD,
186
console_bd_class : C_CONSOLE_BD,
187
console_ft_class : C_CONSOLE_FT,
188
console_controls_class : C_CONSOLE_CONTROLS,
189
console_checkbox_class : C_CHECKBOX,
190
console_pause_class : C_PAUSE,
191
console_pause_label_class : C_PAUSE_LABEL,
192
console_button_class : C_BUTTON,
193
console_clear_class : C_CLEAR,
194
console_title_class : C_CONSOLE_TITLE
198
* Markup template used to generate the DOM structure for the header
199
* section of the Console when it is rendered. The template includes
200
* these {placeholder}s:
203
* <li>console_hd_class - contributed by Console.CHROME_CLASSES</li>
204
* <li>console_title_class - contributed by Console.CHROME_CLASSES</li>
205
* <li>str_title - pulled from attribute strings.title</li>
208
* @property Console.HEADER_TEMPLATE
213
'<div class="{console_hd_class}">'+
214
'<h4 class="{console_title_class}">{str_title}</h4>'+
218
* Markup template used to generate the DOM structure for the Console body
219
* (where the messages are inserted) when it is rendered. The template
220
* includes only the {placeholder} "console_bd_class", which is
221
* constributed by Console.CHROME_CLASSES.
223
* @property Console.BODY_TEMPLATE
227
BODY_TEMPLATE : '<div class="{console_bd_class}"></div>',
230
* Markup template used to generate the DOM structure for the footer
231
* section of the Console when it is rendered. The template includes
232
* many of the {placeholder}s from Console.CHROME_CLASSES as well as:
235
* <li>id_guid - generated unique id, relates the label and checkbox</li>
236
* <li>str_pause - pulled from attribute strings.pause</li>
237
* <li>str_clear - pulled from attribute strings.clear</li>
240
* @property Console.HEADER_TEMPLATE
245
'<div class="{console_ft_class}">'+
246
'<div class="{console_controls_class}">'+
247
'<input type="checkbox" class="{console_checkbox_class} '+
248
'{console_pause_class}" value="1" id="{id_guid}"> '+
249
'<label for="{id_guid}" class="{console_pause_label_class}">'+
250
'{str_pause}</label>' +
251
'<input type="button" class="'+
252
'{console_button_class} {console_clear_class}" '+
253
'value="{str_clear}">'+
258
* Default markup template used to create the DOM structure for Console
259
* entries. The markup contains {placeholder}s for content and classes
260
* that are replaced via Y.substitute. The default template contains
261
* the {placeholder}s identified in Console.ENTRY_CLASSES as well as the
262
* following placeholders that will be populated by the log entry data:
269
* <li>elapsedTime</li>
271
* <li>sourceAndDetail</li>
275
* @property Console.ENTRY_TEMPLATE
280
'<pre class="{entry_class} {cat_class} {src_class}">'+
281
'<div class="{entry_meta_class}">'+
283
'<span class="{entry_cat_class}">'+
285
'<span class="{entry_time_class}">'+
286
' {totalTime}ms (+{elapsedTime}) {localTime}:'+
289
'<p class="{entry_src_class}">'+
293
'<p class="{entry_content_class}">{message}</p>'+
297
* Static property used to define the default attribute configuration of
300
* @property Console.ATTRS
307
* Name of the custom event that will communicate log messages.
309
* @attribute logEvent
320
* Collection of strings used to label elements in the Console UI.
321
* Default collection contains the following name:value pairs:
324
* <li>title : "Log Console"</li>
325
* <li>pause : "Pause"</li>
326
* <li>clear : "Clear"</li>
334
title : "Log Console",
341
* Boolean to pause the outputting of new messages to the console.
342
* When paused, messages will accumulate in the buffer.
350
validator : L.isBoolean
354
* If a category is not specified in the Y.log(..) statement, this
355
* category will be used. Category is also called "log level".
357
* @attribute defaultCategory
367
* If a source is not specified in the Y.log(..) statement, this
368
* source will be used.
370
* @attribute defaultSource
380
* Markup template used to create the DOM structure for Console entries.
382
* @attribute entryTemplate
384
* @default (see Console.ENTRY_TEMPLATE)
392
* Minimum entry log level to render into the Console. The initial
393
* logLevel value for all Console instances defaults from the
394
* Y.config.logLevel YUI configuration, or Console.LOG_LEVEL_INFO if
395
* that configuration is not set.
397
* Possible values are "info", "warn",
398
* "error" (case insensitive), or the corresponding statics
399
* Console.LOG_LEVEL_INFO and so on.
401
* @attribute logLevel
402
* @type String|Number
403
* @default Y.config.logLevel or Console.LOG_LEVEL_INFO
406
value : Y.config.logLevel,
407
validator : function (v) {
408
return this._validateNewLogLevel(v);
411
return this._setLogLevel(v);
416
* Millisecond timeout to maintain before emptying buffer of Console
419
* @attribute printTimeout
429
* Maximum number of Console entries allowed in the Console body at one
430
* time. This is used to keep acquired messages from exploding the
431
* DOM tree and impacting page performance.
433
* @attribute consoleLimit
443
* New entries should display at the top of the Console or the bottom?
445
* @attribute newestOnTop
454
* When new entries are added to the Console UI, should they be
455
* scrolled into view?
457
* @attribute scrollIntoView
466
* The baseline time for this Console instance, used to measure elapsed
467
* time from the moment the console module is <code>use</code>d to the
468
* moment each new entry is logged (not rendered).
470
* This value is reset by the instance method myConsole.reset().
472
* @attribute startTime
474
* @default The moment the console module is <code>use</code>d
481
* The precise time the last entry was logged. Used to measure elapsed
482
* time between log messages.
484
* @attribute lastTime
486
* @default The moment the console module is <code>use</code>d
497
Y.extend(Console,Y.Widget,{
500
* Reference to the Node instance containing the head contents.
510
* Reference to the Node instance that will house the console messages.
520
* Reference to the Node instance containing the footer contents.
530
* Object API returned from <code>Y.later</code>. Holds the timer id
531
* returned by <code>setTimout</code> for scheduling of buffered messages.
541
* Array of normalized message objects awaiting printing.
550
* Wrapper for <code>Y.log</code>.
553
* @param {Any*} * (all arguments passed through to <code>Y.log</code>)
556
return Y.log.apply(Y,arguments);
560
* Clear the console of messages and flush the buffer of pending messages.
562
* @method clearConsole
565
clearConsole : function () {
566
// TODO: clear event listeners from console contents
567
this._body.set(INNER_HTML,'');
569
this._clearTimeout();
577
* Clears the console and resets internal timers.
582
reset : function () {
589
* Outputs all buffered messages to the console UI.
591
* @method printBuffer
594
printBuffer: function () {
595
if (!this.get(PAUSED) && this.get('rendered')) {
597
this._clearTimeout();
599
var messages = this.buffer,
604
// TODO: use doc frag
605
for (i = 0, len = messages.length; i < len; ++i) {
606
this.printLogEntry(messages[i]);
609
this._trimOldEntries();
616
* Prints the provided message to the console UI.
618
* @method printLogEntry
619
* @param m {Object} Normalized message object
622
printLogEntry : function (m) {
624
this._htmlEscapeMessage(m),
625
Console.ENTRY_CLASSES,
627
cat_class : this.getClassName(ENTRY,m.category),
628
src_class : this.getClassName(ENTRY,m.source)
631
var n = create(substitute(this.get('entryTemplate'),m));
633
this._addToConsole(n);
640
* Constructor code. Set up the buffer and entry template, publish
641
* internal events, and subscribe to the configured logEvent.
643
* @method initializer
646
initializer : function () {
649
if (!this.get(ENTRY_TEMPLATE)) {
650
this.set(ENTRY_TEMPLATE,Console.ENTRY_TEMPLATE);
653
Y.on(this.get('logEvent'),Y.bind(this._onLogEvent,this));
656
* Triggers the processing of an incoming message via the default logic
660
* @param event {Event.Facade} An Event Facade object with the following attribute specific properties added:
663
* <dd>The message data normalized into an object literal (see _normalizeMessage)</dd>
665
* @preventable _defEntryFn
667
this.publish(ENTRY, { defaultFn: this._defEntryFn });
670
* Triggers the reset behavior via the default logic in _defResetFn.
673
* @param event {Event.Facade} Event Facade object
674
* @preventable _defResetFn
676
this.publish(RESET, { defaultFn: this._defResetFn });
680
* Generate the Console UI.
685
renderUI : function () {
692
* Sync the UI state to the current attribute state.
696
syncUI : function () {
697
this.set(PAUSED,this.get(PAUSED));
701
* Set up event listeners to wire up the UI to the internal state.
706
bindUI : function () {
707
this.get(CONTENT_BOX).query('input[type=checkbox].'+C_PAUSE).
708
on(CLICK,this._onPauseClick,this);
710
this.get(CONTENT_BOX).query('input[type=button].'+C_CLEAR).
711
on(CLICK,this._onClearClick,this);
714
this.after('stringsChange', this._afterStringsChange);
715
this.after('pausedChange', this._afterPausedChange);
716
this.after('consoleLimitChange', this._afterConsoleLimitChange);
721
* Create the DOM structure for the header elements.
726
_initHead : function () {
727
var cb = this.get(CONTENT_BOX),
728
info = merge(Console.CHROME_CLASSES, {
729
str_title : this.get('strings.title')
732
this._head = create(substitute(Console.HEADER_TEMPLATE,info));
734
cb.insertBefore(this._head,cb.get('firstChild'));
738
* Create the DOM structure for the console body—where messages are
741
* @method _initConsole
744
_initConsole : function () {
745
this._body = create(substitute(
746
Console.BODY_TEMPLATE,
747
Console.CHROME_CLASSES));
749
this.get(CONTENT_BOX).appendChild(this._body);
753
* Create the DOM structure for the footer elements.
758
_initFoot : function () {
759
var info = merge(Console.CHROME_CLASSES, {
761
str_pause : this.get('strings.pause'),
762
str_clear : this.get('strings.clear')
765
this._foot = create(substitute(Console.FOOTER_TEMPLATE,info));
767
this.get(CONTENT_BOX).appendChild(this._foot);
771
* Determine if incoming log messages are within the configured logLevel
772
* to be buffered for printing.
774
* @method _isInLogLevel
777
_isInLogLevel : function (msg,cat) {
778
var lvl = this.get('logLevel'),
779
mlvl = cat === ERROR ? Console.LOG_LEVEL_ERROR :
780
cat === WARN ? Console.LOG_LEVEL_WARN :
781
Console.LOG_LEVEL_INFO;
787
* Create a log entry message from the inputs including the following keys:
789
* <li>time - this moment</li>
790
* <li>message - leg message</li>
791
* <li>category - aka logLevel</li>
792
* <li>source - when provided, the widget or util calling Y.log</li>
793
* <li>sourceAndDetail - same as source but can include instance info</li>
794
* <li>label - logLevel/category label for the entry</li>
795
* <li>localTime - readable version of time</li>
796
* <li>elapsedTime - ms since last entry</li>
797
* <li>totalTime - ms since Console was instantiated or reset</li>
800
* @mehod _normalizeMessage
801
* @param msg {String} the log message
802
* @param cat {String} OPTIONAL the category or logLevel of the message
803
* @param src {String} OPTIONAL the source widget or util of the message
804
* @return Object the message object
807
_normalizeMessage : function (msg,cat,src) {
811
category : cat || this.get('defaultCategory'),
812
sourceAndDetail : src || this.get('defaultSource'),
820
// Extract m.source "Foo" from m.sourceAndDetail "Foo bar baz"
821
m.source = RE_INLINE_SOURCE.test(m.sourceAndDetail) ?
822
RegExp.$1 : m.sourceAndDetail;
823
m.label = m.category;
824
m.localTime = m.time.toLocaleTimeString ?
825
m.time.toLocaleTimeString() : (m.time + '');
826
m.elapsedTime = m.time - this.get(LAST_TIME);
827
m.totalTime = m.time - this.get(START_TIME);
829
this._set(LAST_TIME,m.time);
835
* Sets a timeout for buffered messages to be output to the console.
837
* @method _schedulePrint
840
_schedulePrint : function () {
841
if (!this.get(PAUSED) && !this._timeout) {
842
this._timeout = Y.later(
843
this.get('printTimeout'),
844
this,this.printBuffer);
849
* Inserts a Node into the console body at the top or bottom depending on
850
* the configuration value of newestOnTop.
852
* @method _addToConsole
853
* @param node {Node} the node to insert into the console body
856
_addToConsole : function (node) {
857
var toTop = this.get('newestOnTop'),
861
bd.insertBefore(node,toTop ? bd.get('firstChild') : null);
863
if (this.get('scrollIntoView')) {
864
scrollTop = toTop ? 0 : bd.get('scrollHeight');
866
bd.set('scrollTop', scrollTop);
871
* Performs HTML escaping on strings in the message object.
873
* @method _htmlEscapeMessage
874
* @param m {Object} the normalized message object
875
* @return Object a clone of the message object with proper escapement
878
_htmlEscapeMessage : function (m) {
880
m.message = this._encodeHTML(m.message);
881
m.label = this._encodeHTML(m.label);
882
m.source = this._encodeHTML(m.source);
883
m.sourceAndDetail = this._encodeHTML(m.sourceAndDetail);
884
m.category = this._encodeHTML(m.category);
890
* Removes the oldest message entries from the UI to maintain the limit
891
* specified in the consoleLimit configuration.
893
* @method _trimOldEntries
896
_trimOldEntries : function () {
900
var entries = bd.queryAll(DOT+C_ENTRY),
901
i = entries ? entries.size() - this.get('consoleLimit') : 0;
904
if (this.get('newestOnTop')) {
905
for (var l = entries.size(); i<l; i++) {
906
bd.removeChild(entries.item(i));
910
bd.removeChild(entries.item(i));
918
* Returns the input string with ampersands (&), <, and > encoded
921
* @method _encodeHTML
922
* @param s {String} the raw string
923
* @return String the encoded string
926
_encodeHTML : function (s) {
928
s.replace(RE_AMP,ESC_AMP).
929
replace(RE_LT, ESC_LT).
930
replace(RE_GT, ESC_GT) :
935
* Clears the timeout for printing buffered messages.
937
* @method _clearTimeout
940
_clearTimeout : function () {
942
this._timeout.cancel();
943
this._timeout = null;
948
* Event handler for clicking on the Pause checkbox to update the paused
951
* @method _onPauseClick
952
* @param e {Event} DOM event facade for the click event
955
_onPauseClick : function (e) {
956
var paused = e.target.get(CHECKED);
958
this.set(PAUSED,paused,{ src: Y.Widget.UI_SRC });
962
* Event handler for clicking on the Clear button. Pass-through to
963
* <code>this.clearConsole()</code>.
965
* @method _onClearClick
966
* @param e {Event} DOM event facade for the click event
969
_onClearClick : function (e) {
975
* Setter method for logLevel attribute. Acceptable values are
976
* "error", "warn", "info", and
977
* Y.Console.LOG_LEVEL_ERROR, Y.Console.LOG_LEVEL_WARN,
978
* Y.Console.LOG_LEVEL_INFO. Any other value becomes
979
* Y.Console.LOG_LEVEL_INFO.
981
* @method _setLogLevel
982
* @param v {String|Number} String or numeric alias for the desired logLevel
983
* @return Number LOG_LEVEL_ERROR, _WARN, or _INFO
986
_setLogLevel : function (v) {
990
Console.LOG_LEVEL_ERROR :
992
Console.LOG_LEVEL_WARN :
993
Console.LOG_LEVEL_INFO;
994
} else if (!isNumber(v)) {
995
v = Console.LOG_LEVEL_INFO;
1002
* Verifies input logLevel is one of Y.Console.LOG_LEVEL_ERROR,
1003
* Y.Console.LOG_LEVEL_WARN, or Y.Console.LOG_LEVEL_INFO.
1005
* @method _validateNewLogLevel
1006
* @param v {Number} requested logLevel
1010
_validateNewLogLevel : function (v) {
1011
return v === Console.LOG_LEVEL_INFO ||
1012
v === Console.LOG_LEVEL_WARN ||
1013
v === Console.LOG_LEVEL_ERROR;
1017
* Updates the UI if changes are made to any of the strings in the strings
1020
* @method _afterStringsChange
1021
* @param e {Event} Custom event for the attribute change
1024
_afterStringsChange : function (e) {
1025
var prop = e.subAttrName ? e.subAttrName.split(DOT)[1] : null,
1026
cb = this.get(CONTENT_BOX),
1031
if ((!prop || prop === TITLE) && before.title !== after.title) {
1032
el = cb.query(DOT+C_CONSOLE_TITLE);
1034
el.set(INNER_HTML,after.title);
1038
if ((!prop || prop === PAUSE) && before.pause !== after.pause) {
1039
el = cb.query(DOT+C_PAUSE_LABEL);
1041
el.set(INNER_HTML,after.pause);
1045
if ((!prop || prop === CLEAR) && before.clear !== after.clear) {
1046
el = cb.query(DOT+C_CLEAR);
1048
el.set('value',after.clear);
1054
* Updates the UI and schedules or cancels the scheduled buffer printing
1057
* @method _afterPausedChange
1058
* @param e {Event} Custom event for the attribute change
1061
_afterPausedChange : function (e) {
1062
var paused = e.newVal;
1064
if (e.src !== Y.Widget.SRC_UI) {
1065
var node = this._foot.queryAll('input[type=checkbox].'+C_PAUSE);
1067
node.set(CHECKED,paused);
1072
this._schedulePrint();
1073
} else if (this._timeout) {
1074
clearTimeout(this._timeout);
1075
this._timeout = null;
1080
* Calls this._trimOldEntries() in response to changes in the configured
1081
* consoleLimit attribute.
1083
* @method _afterConsoleLimitChange
1084
* @param e {Event} Custom event for the attribute change
1087
_afterConsoleLimitChange : function () {
1088
this._trimOldEntries();
1093
* Responds to log events by normalizing qualifying messages and passing
1094
* them along through the entry event for buffering etc.
1096
* @method _onLogEvent
1097
* @param msg {String} the log message
1098
* @param cat {String} OPTIONAL the category or logLevel of the message
1099
* @param src {String} OPTIONAL the source of the message (e.g. widget name)
1102
_onLogEvent : function (msg,cat,src) {
1104
if (!this.get(DISABLED) && this._isInLogLevel(msg,cat,src)) {
1107
var debug = Y.config.debug;
1108
Y.config.debug = false;
1111
message : this._normalizeMessage.apply(this,arguments)
1114
Y.config.debug = debug;
1119
* Clears the console, resets the startTime attribute, enables and
1120
* unpauses the widget.
1122
* @method _defResetFn
1125
_defResetFn : function () {
1126
this.clearConsole();
1127
this.set(START_TIME,new Date());
1128
this.set(DISABLED,false);
1129
this.set(PAUSED,false);
1133
* Buffers incoming message objects and schedules the printing.
1135
* @method _defEntryFn
1136
* @param e {Event} The Custom event carrying the message in its payload
1139
_defEntryFn : function (e) {
1141
this.buffer.push(e.message);
1142
this._schedulePrint();
1148
Y.Console = Console;
1151
}, '3.0.0pr2' ,{requires:['substitute','widget']});