|
|
|
|
/*
|
|
|
|
|
Blackbird - Open Source JavaScript Logging Utility
|
|
|
|
|
Author: G Scott Olson
|
|
|
|
|
Web: http://blackbirdjs.googlecode.com/
|
|
|
|
|
http://www.gscottolson.com/blackbirdjs/
|
|
|
|
|
Version: 1.0
|
|
|
|
|
|
|
|
|
|
The MIT License - Copyright (c) 2008 Blackbird Project
|
|
|
|
|
*/
|
|
|
|
|
(function() {
|
|
|
|
|
var NAMESPACE = 'log';
|
|
|
|
|
var IE6_POSITION_FIXED = true; // enable IE6 {position:fixed}
|
|
|
|
|
|
|
|
|
|
var bbird;
|
|
|
|
|
var outputList;
|
|
|
|
|
var cache = [];
|
|
|
|
|
|
|
|
|
|
var state = getState();
|
|
|
|
|
var classes = {};
|
|
|
|
|
var profiler = {};
|
|
|
|
|
var IDs = {
|
|
|
|
|
blackbird: 'blackbird',
|
|
|
|
|
checkbox: 'bbVis',
|
|
|
|
|
filters: 'bbFilters',
|
|
|
|
|
controls: 'bbControls',
|
|
|
|
|
size: 'bbSize'
|
|
|
|
|
}
|
|
|
|
|
var prevtracetime;
|
|
|
|
|
var messageTypes = { //order of these properties imply render order of filter controls
|
|
|
|
|
debug: true,
|
|
|
|
|
info: true,
|
|
|
|
|
warn: true,
|
|
|
|
|
error: true,
|
|
|
|
|
profile: true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function generateMarkup() { //build markup
|
|
|
|
|
var spans = [];
|
|
|
|
|
for (type in messageTypes) {
|
|
|
|
|
spans.push(['<span class="', type, '" type="', type, '"></span>'].join(''));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var newNode = document.createElement('DIV');
|
|
|
|
|
newNode.id = IDs.blackbird;
|
|
|
|
|
newNode.style.display = 'none';
|
|
|
|
|
newNode.innerHTML = [
|
|
|
|
|
'<div class="header">',
|
|
|
|
|
'<div class="left">',
|
|
|
|
|
'<div id="', IDs.filters, '" class="filters" title="click to filter by message type">', spans.join(''), '</div>',
|
|
|
|
|
'</div>',
|
|
|
|
|
'<div class="right">',
|
|
|
|
|
'<div id="', IDs.controls, '" class="controls">',
|
|
|
|
|
'<span id="', IDs.size, '" title="contract" op="resize"></span>',
|
|
|
|
|
'<span class="clear" title="clear" op="clear"></span>',
|
|
|
|
|
'<span class="close" title="close" op="close"></span>',
|
|
|
|
|
'</div>',
|
|
|
|
|
'</div>',
|
|
|
|
|
'</div>',
|
|
|
|
|
'<div class="main">',
|
|
|
|
|
'<div class="left"></div><div class="mainBody">',
|
|
|
|
|
'<ol>', cache.join(''), '</ol>',
|
|
|
|
|
'</div><div class="right"></div>',
|
|
|
|
|
'</div>',
|
|
|
|
|
'<div class="footer">',
|
|
|
|
|
'<div class="left"><label for="', IDs.checkbox, '"><input type="checkbox" id="', IDs.checkbox, '" />Visible on page load</label></div>',
|
|
|
|
|
'<div class="right"></div>',
|
|
|
|
|
'</div>'
|
|
|
|
|
].join('');
|
|
|
|
|
return newNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function backgroundImage() { //(IE6 only) change <BODY> tag's background to resolve {position:fixed} support
|
|
|
|
|
var bodyTag = document.getElementsByTagName('BODY')[0];
|
|
|
|
|
|
|
|
|
|
if (bodyTag.currentStyle && IE6_POSITION_FIXED) {
|
|
|
|
|
if (bodyTag.currentStyle.backgroundImage == 'none') {
|
|
|
|
|
bodyTag.style.backgroundImage = 'url(about:blank)';
|
|
|
|
|
}
|
|
|
|
|
if (bodyTag.currentStyle.backgroundAttachment == 'scroll') {
|
|
|
|
|
bodyTag.style.backgroundAttachment = 'fixed';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addMessage(type, content) { //adds a message to the output list
|
|
|
|
|
content = (content.constructor == Array) ? content.join('') : content;
|
|
|
|
|
if (outputList) {
|
|
|
|
|
var newMsg = document.createElement('LI');
|
|
|
|
|
newMsg.className = type;
|
|
|
|
|
newMsg.innerHTML = ['<span class="icon"></span>', content].join('');
|
|
|
|
|
outputList.appendChild(newMsg);
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
} else {
|
|
|
|
|
cache.push(['<li class="', type, '"><span class="icon"></span>', content, '</li>'].join(''));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clear() { //clear list output
|
|
|
|
|
outputList.innerHTML = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clickControl(evt) {
|
|
|
|
|
if (!evt) evt = window.event;
|
|
|
|
|
var el = (evt.target) ? evt.target : evt.srcElement;
|
|
|
|
|
|
|
|
|
|
if (el.tagName == 'SPAN') {
|
|
|
|
|
switch (el.getAttributeNode('op').nodeValue) {
|
|
|
|
|
case 'resize': resize(); break;
|
|
|
|
|
case 'clear': clear(); break;
|
|
|
|
|
case 'close': hide(); break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clickFilter(evt) { //show/hide a specific message type
|
|
|
|
|
if (!evt) evt = window.event;
|
|
|
|
|
var span = (evt.target) ? evt.target : evt.srcElement;
|
|
|
|
|
|
|
|
|
|
if (span && span.tagName == 'SPAN') {
|
|
|
|
|
|
|
|
|
|
var type = span.getAttributeNode('type').nodeValue;
|
|
|
|
|
|
|
|
|
|
if (evt.altKey) {
|
|
|
|
|
var filters = document.getElementById(IDs.filters).getElementsByTagName('SPAN');
|
|
|
|
|
|
|
|
|
|
var active = 0;
|
|
|
|
|
for (entry in messageTypes) {
|
|
|
|
|
if (messageTypes[entry]) active++;
|
|
|
|
|
}
|
|
|
|
|
var oneActiveFilter = (active == 1 && messageTypes[type]);
|
|
|
|
|
|
|
|
|
|
for (var i = 0; filters[i]; i++) {
|
|
|
|
|
var spanType = filters[i].getAttributeNode('type').nodeValue;
|
|
|
|
|
|
|
|
|
|
filters[i].className = (oneActiveFilter || (spanType == type)) ? spanType : spanType + 'Disabled';
|
|
|
|
|
messageTypes[spanType] = oneActiveFilter || (spanType == type);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
messageTypes[type] = !messageTypes[type];
|
|
|
|
|
span.className = (messageTypes[type]) ? type : type + 'Disabled';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//build outputList's class from messageTypes object
|
|
|
|
|
var disabledTypes = [];
|
|
|
|
|
for (type in messageTypes) {
|
|
|
|
|
if (!messageTypes[type]) disabledTypes.push(type);
|
|
|
|
|
}
|
|
|
|
|
disabledTypes.push('');
|
|
|
|
|
outputList.className = disabledTypes.join('Hidden ');
|
|
|
|
|
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clickVis(evt) {
|
|
|
|
|
if (!evt) evt = window.event;
|
|
|
|
|
var el = (evt.target) ? evt.target : evt.srcElement;
|
|
|
|
|
|
|
|
|
|
state.load = el.checked;
|
|
|
|
|
setState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function scrollToBottom() { //scroll list output to the bottom
|
|
|
|
|
outputList.scrollTop = outputList.scrollHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isVisible() { //determine the visibility
|
|
|
|
|
return (bbird.style.display == 'block');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hide() {
|
|
|
|
|
bbird.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function show() {
|
|
|
|
|
var body = document.getElementsByTagName('BODY')[0];
|
|
|
|
|
body.removeChild(bbird);
|
|
|
|
|
body.appendChild(bbird);
|
|
|
|
|
bbird.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//sets the position
|
|
|
|
|
function reposition(position) {
|
|
|
|
|
if (position === undefined || position == null) {
|
|
|
|
|
position = (state && state.pos === null) ? 1 : (state.pos + 1) % 4; //set to initial position ('topRight') or move to next position
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (position) {
|
|
|
|
|
case 0: classes[0] = 'bbTopLeft'; break;
|
|
|
|
|
case 1: classes[0] = 'bbTopRight'; break;
|
|
|
|
|
case 2: classes[0] = 'bbBottomLeft'; break;
|
|
|
|
|
case 3: classes[0] = 'bbBottomRight'; break;
|
|
|
|
|
}
|
|
|
|
|
state.pos = position;
|
|
|
|
|
setState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resize(size) {
|
|
|
|
|
if (size === undefined || size === null) {
|
|
|
|
|
size = (state && state.size == null) ? 0 : (state.size + 1) % 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
classes[1] = (size === 0) ? 'bbSmall' : 'bbLarge'
|
|
|
|
|
|
|
|
|
|
var span = document.getElementById(IDs.size);
|
|
|
|
|
span.title = (size === 1) ? 'small' : 'large';
|
|
|
|
|
span.className = span.title;
|
|
|
|
|
|
|
|
|
|
state.size = size;
|
|
|
|
|
setState();
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setState() {
|
|
|
|
|
var props = [];
|
|
|
|
|
for (entry in state) {
|
|
|
|
|
var value = (state[entry] && state[entry].constructor === String) ? '"' + state[entry] + '"' : state[entry];
|
|
|
|
|
props.push(entry + ':' + value);
|
|
|
|
|
}
|
|
|
|
|
props = props.join(',');
|
|
|
|
|
|
|
|
|
|
var expiration = new Date();
|
|
|
|
|
expiration.setDate(expiration.getDate() + 14);
|
|
|
|
|
document.cookie = ['blackbird={', props, '}; expires=', expiration.toUTCString(), ';'].join('');
|
|
|
|
|
|
|
|
|
|
var newClass = [];
|
|
|
|
|
for (word in classes) {
|
|
|
|
|
newClass.push(classes[word]);
|
|
|
|
|
}
|
|
|
|
|
if (bbird != null) {
|
|
|
|
|
bbird.className = newClass.join(' ');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getState() {
|
|
|
|
|
var re = new RegExp(/blackbird=({[^;]+})(;|\b|$)/);
|
|
|
|
|
var match = re.exec(document.cookie);
|
|
|
|
|
return (match && match[1]) ? eval('(' + match[1] + ')') : { pos: null, size: null, load: null };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//event handler for 'keyup' event for window
|
|
|
|
|
function readKey(evt) {
|
|
|
|
|
if (!evt) evt = window.event;
|
|
|
|
|
var code = 113; //F2 key
|
|
|
|
|
|
|
|
|
|
if (evt && evt.keyCode == code) {
|
|
|
|
|
|
|
|
|
|
var visible = isVisible();
|
|
|
|
|
|
|
|
|
|
if (visible && evt.shiftKey && evt.altKey) clear();
|
|
|
|
|
else if (visible && evt.shiftKey) reposition();
|
|
|
|
|
else if (!evt.shiftKey && !evt.altKey) {
|
|
|
|
|
(visible) ? hide() : show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//event management ( thanks John Resig )
|
|
|
|
|
function addEvent(obj, type, fn) {
|
|
|
|
|
var obj = (obj.constructor === String) ? document.getElementById(obj) : obj;
|
|
|
|
|
if (obj.attachEvent) {
|
|
|
|
|
obj['e' + type + fn] = fn;
|
|
|
|
|
obj[type + fn] = function() { obj['e' + type + fn](window.event) };
|
|
|
|
|
obj.attachEvent('on' + type, obj[type + fn]);
|
|
|
|
|
} else obj.addEventListener(type, fn, false);
|
|
|
|
|
}
|
|
|
|
|
function removeEvent(obj, type, fn) {
|
|
|
|
|
var obj = (obj.constructor === String) ? document.getElementById(obj) : obj;
|
|
|
|
|
if (obj.detachEvent) {
|
|
|
|
|
obj.detachEvent('on' + type, obj[type + fn]);
|
|
|
|
|
obj[type + fn] = null;
|
|
|
|
|
} else obj.removeEventListener(type, fn, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window[NAMESPACE] = {
|
|
|
|
|
toggle:
|
|
|
|
|
function() { (isVisible()) ? hide() : show(); },
|
|
|
|
|
resize:
|
|
|
|
|
function() { resize(); },
|
|
|
|
|
clear:
|
|
|
|
|
function() { clear(); },
|
|
|
|
|
move:
|
|
|
|
|
function() { reposition(); },
|
|
|
|
|
debug:
|
|
|
|
|
function(msg) { addMessage('debug', msg); },
|
|
|
|
|
warn:
|
|
|
|
|
function(msg) { addMessage('warn', msg); },
|
|
|
|
|
info:
|
|
|
|
|
function(msg) { addMessage('info', msg); },
|
|
|
|
|
error:
|
|
|
|
|
function(msg) { addMessage('error', msg); },
|
|
|
|
|
trace:
|
|
|
|
|
function(msg) {
|
|
|
|
|
msg += new Date().toLocaleString();
|
|
|
|
|
addMessage('debug', msg);
|
|
|
|
|
prevtracetime = new Date();
|
|
|
|
|
},
|
|
|
|
|
diff:
|
|
|
|
|
function(msg) {
|
|
|
|
|
if (prevtracetime == null) {
|
|
|
|
|
addMessage("debug", "上次记录时间不存在");
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
var now = new Date();
|
|
|
|
|
var diff = now.getTime() - prevtracetime.getTime();
|
|
|
|
|
if (msg == undefined || msg == "") {
|
|
|
|
|
addMessage('debug', '距离上次记录过去了' + diff + '毫秒');
|
|
|
|
|
} else {
|
|
|
|
|
addMessage('debug', msg + ":" + diff);
|
|
|
|
|
}
|
|
|
|
|
prevtracetime = new Date();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
profile:
|
|
|
|
|
function(label) {
|
|
|
|
|
var currentTime = new Date(); //record the current time when profile() is executed
|
|
|
|
|
|
|
|
|
|
if (label == undefined || label == '') {
|
|
|
|
|
addMessage('error', '<b>ERROR:</b> Please specify a label for your profile statement');
|
|
|
|
|
}
|
|
|
|
|
else if (profiler[label]) {
|
|
|
|
|
addMessage('profile', [label, ': ', currentTime - profiler[label], 'ms'].join(''));
|
|
|
|
|
delete profiler[label];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
profiler[label] = currentTime;
|
|
|
|
|
addMessage('profile', label);
|
|
|
|
|
}
|
|
|
|
|
return currentTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addEvent(window, 'load',
|
|
|
|
|
/* initialize Blackbird when the page loads */
|
|
|
|
|
function() {
|
|
|
|
|
var body = document.getElementsByTagName('BODY')[0];
|
|
|
|
|
bbird = body.appendChild(generateMarkup());
|
|
|
|
|
outputList = bbird.getElementsByTagName('OL')[0];
|
|
|
|
|
|
|
|
|
|
backgroundImage();
|
|
|
|
|
|
|
|
|
|
//add events
|
|
|
|
|
addEvent(IDs.checkbox, 'click', clickVis);
|
|
|
|
|
addEvent(IDs.filters, 'click', clickFilter);
|
|
|
|
|
addEvent(IDs.controls, 'click', clickControl);
|
|
|
|
|
addEvent(document, 'keyup', readKey);
|
|
|
|
|
|
|
|
|
|
resize(state.size);
|
|
|
|
|
reposition(state.pos);
|
|
|
|
|
if (state.load) {
|
|
|
|
|
show();
|
|
|
|
|
document.getElementById(IDs.checkbox).checked = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
|
|
|
|
|
window[NAMESPACE].init = function() {
|
|
|
|
|
show();
|
|
|
|
|
window[NAMESPACE].error(['<b>', NAMESPACE, '</b> can only be initialized once']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addEvent(window, 'unload', function() {
|
|
|
|
|
removeEvent(IDs.checkbox, 'click', clickVis);
|
|
|
|
|
removeEvent(IDs.filters, 'click', clickFilter);
|
|
|
|
|
removeEvent(IDs.controls, 'click', clickControl);
|
|
|
|
|
removeEvent(document, 'keyup', readKey);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
})();
|