<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> | |
<head> | |
<title>log4javascript</title> | |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
<!-- Make IE8 behave like IE7, having gone to all the trouble of making IE work --> | |
<meta http-equiv="X-UA-Compatible" content="IE=7" /> | |
<script type="text/javascript">var isIe = false, isIePre7 = false;</script> | |
<!--[if IE]><script type="text/javascript">isIe = true</script><![endif]--> | |
<!--[if lt IE 7]><script type="text/javascript">isIePre7 = true</script><![endif]--> | |
<script type="text/javascript"> | |
//<![CDATA[ | |
var loggingEnabled = true; | |
var logQueuedEventsTimer = null; | |
var logEntries = []; | |
var logEntriesAndSeparators = []; | |
var logItems = []; | |
var renderDelay = 100; | |
var unrenderedLogItemsExist = false; | |
var rootGroup, currentGroup = null; | |
var loaded = false; | |
var currentLogItem = null; | |
var logMainContainer; | |
function copyProperties(obj, props) { | |
for (var i in props) { | |
obj[i] = props[i]; | |
} | |
} | |
/*----------------------------------------------------------------*/ | |
function LogItem() { | |
} | |
LogItem.prototype = { | |
mainContainer: null, | |
wrappedContainer: null, | |
unwrappedContainer: null, | |
group: null, | |
appendToLog: function() { | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].appendToLog(); | |
} | |
this.group.update(); | |
}, | |
doRemove: function(doUpdate, removeFromGroup) { | |
if (this.rendered) { | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].remove(); | |
} | |
this.unwrappedElementContainer = null; | |
this.wrappedElementContainer = null; | |
this.mainElementContainer = null; | |
} | |
if (this.group && removeFromGroup) { | |
this.group.removeChild(this, doUpdate); | |
} | |
if (this === currentLogItem) { | |
currentLogItem = null; | |
} | |
}, | |
remove: function(doUpdate, removeFromGroup) { | |
this.doRemove(doUpdate, removeFromGroup); | |
}, | |
render: function() {}, | |
accept: function(visitor) { | |
visitor.visit(this); | |
}, | |
getUnwrappedDomContainer: function() { | |
return this.group.unwrappedElementContainer.contentDiv; | |
}, | |
getWrappedDomContainer: function() { | |
return this.group.wrappedElementContainer.contentDiv; | |
}, | |
getMainDomContainer: function() { | |
return this.group.mainElementContainer.contentDiv; | |
} | |
}; | |
LogItem.serializedItemKeys = {LOG_ENTRY: 0, GROUP_START: 1, GROUP_END: 2}; | |
/*----------------------------------------------------------------*/ | |
function LogItemContainerElement() { | |
} | |
LogItemContainerElement.prototype = { | |
appendToLog: function() { | |
var insertBeforeFirst = (newestAtTop && this.containerDomNode.hasChildNodes()); | |
if (insertBeforeFirst) { | |
this.containerDomNode.insertBefore(this.mainDiv, this.containerDomNode.firstChild); | |
} else { | |
this.containerDomNode.appendChild(this.mainDiv); | |
} | |
} | |
}; | |
/*----------------------------------------------------------------*/ | |
function SeparatorElementContainer(containerDomNode) { | |
this.containerDomNode = containerDomNode; | |
this.mainDiv = document.createElement("div"); | |
this.mainDiv.className = "separator"; | |
this.mainDiv.innerHTML = " "; | |
} | |
SeparatorElementContainer.prototype = new LogItemContainerElement(); | |
SeparatorElementContainer.prototype.remove = function() { | |
this.mainDiv.parentNode.removeChild(this.mainDiv); | |
this.mainDiv = null; | |
}; | |
/*----------------------------------------------------------------*/ | |
function Separator() { | |
this.rendered = false; | |
} | |
Separator.prototype = new LogItem(); | |
copyProperties(Separator.prototype, { | |
render: function() { | |
var containerDomNode = this.group.contentDiv; | |
if (isIe) { | |
this.unwrappedElementContainer = new SeparatorElementContainer(this.getUnwrappedDomContainer()); | |
this.wrappedElementContainer = new SeparatorElementContainer(this.getWrappedDomContainer()); | |
this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer]; | |
} else { | |
this.mainElementContainer = new SeparatorElementContainer(this.getMainDomContainer()); | |
this.elementContainers = [this.mainElementContainer]; | |
} | |
this.content = this.formattedMessage; | |
this.rendered = true; | |
} | |
}); | |
/*----------------------------------------------------------------*/ | |
function GroupElementContainer(group, containerDomNode, isRoot, isWrapped) { | |
this.group = group; | |
this.containerDomNode = containerDomNode; | |
this.isRoot = isRoot; | |
this.isWrapped = isWrapped; | |
this.expandable = false; | |
if (this.isRoot) { | |
if (isIe) { | |
this.contentDiv = logMainContainer.appendChild(document.createElement("div")); | |
this.contentDiv.id = this.isWrapped ? "log_wrapped" : "log_unwrapped"; | |
} else { | |
this.contentDiv = logMainContainer; | |
} | |
} else { | |
var groupElementContainer = this; | |
this.mainDiv = document.createElement("div"); | |
this.mainDiv.className = "group"; | |
this.headingDiv = this.mainDiv.appendChild(document.createElement("div")); | |
this.headingDiv.className = "groupheading"; | |
this.expander = this.headingDiv.appendChild(document.createElement("span")); | |
this.expander.className = "expander unselectable greyedout"; | |
this.expander.unselectable = true; | |
var expanderText = this.group.expanded ? "-" : "+"; | |
this.expanderTextNode = this.expander.appendChild(document.createTextNode(expanderText)); | |
this.headingDiv.appendChild(document.createTextNode(" " + this.group.name)); | |
this.contentDiv = this.mainDiv.appendChild(document.createElement("div")); | |
var contentCssClass = this.group.expanded ? "expanded" : "collapsed"; | |
this.contentDiv.className = "groupcontent " + contentCssClass; | |
this.expander.onclick = function() { | |
if (groupElementContainer.group.expandable) { | |
groupElementContainer.group.toggleExpanded(); | |
} | |
}; | |
} | |
} | |
GroupElementContainer.prototype = new LogItemContainerElement(); | |
copyProperties(GroupElementContainer.prototype, { | |
toggleExpanded: function() { | |
if (!this.isRoot) { | |
var oldCssClass, newCssClass, expanderText; | |
if (this.group.expanded) { | |
newCssClass = "expanded"; | |
oldCssClass = "collapsed"; | |
expanderText = "-"; | |
} else { | |
newCssClass = "collapsed"; | |
oldCssClass = "expanded"; | |
expanderText = "+"; | |
} | |
replaceClass(this.contentDiv, newCssClass, oldCssClass); | |
this.expanderTextNode.nodeValue = expanderText; | |
} | |
}, | |
remove: function() { | |
if (!this.isRoot) { | |
this.headingDiv = null; | |
this.expander.onclick = null; | |
this.expander = null; | |
this.expanderTextNode = null; | |
this.contentDiv = null; | |
this.containerDomNode = null; | |
this.mainDiv.parentNode.removeChild(this.mainDiv); | |
this.mainDiv = null; | |
} | |
}, | |
reverseChildren: function() { | |
// Invert the order of the log entries | |
var node = null; | |
// Remove all the log container nodes | |
var childDomNodes = []; | |
while ((node = this.contentDiv.firstChild)) { | |
this.contentDiv.removeChild(node); | |
childDomNodes.push(node); | |
} | |
// Put them all back in reverse order | |
while ((node = childDomNodes.pop())) { | |
this.contentDiv.appendChild(node); | |
} | |
}, | |
update: function() { | |
if (!this.isRoot) { | |
if (this.group.expandable) { | |
removeClass(this.expander, "greyedout"); | |
} else { | |
addClass(this.expander, "greyedout"); | |
} | |
} | |
}, | |
clear: function() { | |
if (this.isRoot) { | |
this.contentDiv.innerHTML = ""; | |
} | |
} | |
}); | |
/*----------------------------------------------------------------*/ | |
function Group(name, isRoot, initiallyExpanded) { | |
this.name = name; | |
this.group = null; | |
this.isRoot = isRoot; | |
this.initiallyExpanded = initiallyExpanded; | |
this.elementContainers = []; | |
this.children = []; | |
this.expanded = initiallyExpanded; | |
this.rendered = false; | |
this.expandable = false; | |
} | |
Group.prototype = new LogItem(); | |
copyProperties(Group.prototype, { | |
addChild: function(logItem) { | |
this.children.push(logItem); | |
logItem.group = this; | |
}, | |
render: function() { | |
if (isIe) { | |
var unwrappedDomContainer, wrappedDomContainer; | |
if (this.isRoot) { | |
unwrappedDomContainer = logMainContainer; | |
wrappedDomContainer = logMainContainer; | |
} else { | |
unwrappedDomContainer = this.getUnwrappedDomContainer(); | |
wrappedDomContainer = this.getWrappedDomContainer(); | |
} | |
this.unwrappedElementContainer = new GroupElementContainer(this, unwrappedDomContainer, this.isRoot, false); | |
this.wrappedElementContainer = new GroupElementContainer(this, wrappedDomContainer, this.isRoot, true); | |
this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer]; | |
} else { | |
var mainDomContainer = this.isRoot ? logMainContainer : this.getMainDomContainer(); | |
this.mainElementContainer = new GroupElementContainer(this, mainDomContainer, this.isRoot, false); | |
this.elementContainers = [this.mainElementContainer]; | |
} | |
this.rendered = true; | |
}, | |
toggleExpanded: function() { | |
this.expanded = !this.expanded; | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].toggleExpanded(); | |
} | |
}, | |
expand: function() { | |
if (!this.expanded) { | |
this.toggleExpanded(); | |
} | |
}, | |
accept: function(visitor) { | |
visitor.visitGroup(this); | |
}, | |
reverseChildren: function() { | |
if (this.rendered) { | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].reverseChildren(); | |
} | |
} | |
}, | |
update: function() { | |
var previouslyExpandable = this.expandable; | |
this.expandable = (this.children.length !== 0); | |
if (this.expandable !== previouslyExpandable) { | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].update(); | |
} | |
} | |
}, | |
flatten: function() { | |
var visitor = new GroupFlattener(); | |
this.accept(visitor); | |
return visitor.logEntriesAndSeparators; | |
}, | |
removeChild: function(child, doUpdate) { | |
array_remove(this.children, child); | |
child.group = null; | |
if (doUpdate) { | |
this.update(); | |
} | |
}, | |
remove: function(doUpdate, removeFromGroup) { | |
for (var i = 0, len = this.children.length; i < len; i++) { | |
this.children[i].remove(false, false); | |
} | |
this.children = []; | |
this.update(); | |
if (this === currentGroup) { | |
currentGroup = this.group; | |
} | |
this.doRemove(doUpdate, removeFromGroup); | |
}, | |
serialize: function(items) { | |
items.push([LogItem.serializedItemKeys.GROUP_START, this.name]); | |
for (var i = 0, len = this.children.length; i < len; i++) { | |
this.children[i].serialize(items); | |
} | |
if (this !== currentGroup) { | |
items.push([LogItem.serializedItemKeys.GROUP_END]); | |
} | |
}, | |
clear: function() { | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].clear(); | |
} | |
} | |
}); | |
/*----------------------------------------------------------------*/ | |
function LogEntryElementContainer() { | |
} | |
LogEntryElementContainer.prototype = new LogItemContainerElement(); | |
copyProperties(LogEntryElementContainer.prototype, { | |
remove: function() { | |
this.doRemove(); | |
}, | |
doRemove: function() { | |
this.mainDiv.parentNode.removeChild(this.mainDiv); | |
this.mainDiv = null; | |
this.contentElement = null; | |
this.containerDomNode = null; | |
}, | |
setContent: function(content, wrappedContent) { | |
if (content === this.formattedMessage) { | |
this.contentElement.innerHTML = ""; | |
this.contentElement.appendChild(document.createTextNode(this.formattedMessage)); | |
} else { | |
this.contentElement.innerHTML = content; | |
} | |
}, | |
setSearchMatch: function(isMatch) { | |
var oldCssClass = isMatch ? "searchnonmatch" : "searchmatch"; | |
var newCssClass = isMatch ? "searchmatch" : "searchnonmatch"; | |
replaceClass(this.mainDiv, newCssClass, oldCssClass); | |
}, | |
clearSearch: function() { | |
removeClass(this.mainDiv, "searchmatch"); | |
removeClass(this.mainDiv, "searchnonmatch"); | |
} | |
}); | |
/*----------------------------------------------------------------*/ | |
function LogEntryWrappedElementContainer(logEntry, containerDomNode) { | |
this.logEntry = logEntry; | |
this.containerDomNode = containerDomNode; | |
this.mainDiv = document.createElement("div"); | |
this.mainDiv.appendChild(document.createTextNode(this.logEntry.formattedMessage)); | |
this.mainDiv.className = "logentry wrapped " + this.logEntry.level; | |
this.contentElement = this.mainDiv; | |
} | |
LogEntryWrappedElementContainer.prototype = new LogEntryElementContainer(); | |
LogEntryWrappedElementContainer.prototype.setContent = function(content, wrappedContent) { | |
if (content === this.formattedMessage) { | |
this.contentElement.innerHTML = ""; | |
this.contentElement.appendChild(document.createTextNode(this.formattedMessage)); | |
} else { | |
this.contentElement.innerHTML = wrappedContent; | |
} | |
}; | |
/*----------------------------------------------------------------*/ | |
function LogEntryUnwrappedElementContainer(logEntry, containerDomNode) { | |
this.logEntry = logEntry; | |
this.containerDomNode = containerDomNode; | |
this.mainDiv = document.createElement("div"); | |
this.mainDiv.className = "logentry unwrapped " + this.logEntry.level; | |
this.pre = this.mainDiv.appendChild(document.createElement("pre")); | |
this.pre.appendChild(document.createTextNode(this.logEntry.formattedMessage)); | |
this.pre.className = "unwrapped"; | |
this.contentElement = this.pre; | |
} | |
LogEntryUnwrappedElementContainer.prototype = new LogEntryElementContainer(); | |
LogEntryUnwrappedElementContainer.prototype.remove = function() { | |
this.doRemove(); | |
this.pre = null; | |
}; | |
/*----------------------------------------------------------------*/ | |
function LogEntryMainElementContainer(logEntry, containerDomNode) { | |
this.logEntry = logEntry; | |
this.containerDomNode = containerDomNode; | |
this.mainDiv = document.createElement("div"); | |
this.mainDiv.className = "logentry nonielogentry " + this.logEntry.level; | |
this.contentElement = this.mainDiv.appendChild(document.createElement("span")); | |
this.contentElement.appendChild(document.createTextNode(this.logEntry.formattedMessage)); | |
} | |
LogEntryMainElementContainer.prototype = new LogEntryElementContainer(); | |
/*----------------------------------------------------------------*/ | |
function LogEntry(level, formattedMessage) { | |
this.level = level; | |
this.formattedMessage = formattedMessage; | |
this.rendered = false; | |
} | |
LogEntry.prototype = new LogItem(); | |
copyProperties(LogEntry.prototype, { | |
render: function() { | |
var logEntry = this; | |
var containerDomNode = this.group.contentDiv; | |
// Support for the CSS attribute white-space in IE for Windows is | |
// non-existent pre version 6 and slightly odd in 6, so instead | |
// use two different HTML elements | |
if (isIe) { | |
this.formattedMessage = this.formattedMessage.replace(/\r\n/g, "\r"); // Workaround for IE's treatment of white space | |
this.unwrappedElementContainer = new LogEntryUnwrappedElementContainer(this, this.getUnwrappedDomContainer()); | |
this.wrappedElementContainer = new LogEntryWrappedElementContainer(this, this.getWrappedDomContainer()); | |
this.elementContainers = [this.unwrappedElementContainer, this.wrappedElementContainer]; | |
} else { | |
this.mainElementContainer = new LogEntryMainElementContainer(this, this.getMainDomContainer()); | |
this.elementContainers = [this.mainElementContainer]; | |
} | |
this.content = this.formattedMessage; | |
this.rendered = true; | |
}, | |
setContent: function(content, wrappedContent) { | |
if (content != this.content) { | |
if (isIe && (content !== this.formattedMessage)) { | |
content = content.replace(/\r\n/g, "\r"); // Workaround for IE's treatment of white space | |
} | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].setContent(content, wrappedContent); | |
} | |
this.content = content; | |
} | |
}, | |
getSearchMatches: function() { | |
var matches = []; | |
var i, len; | |
if (isIe) { | |
var unwrappedEls = getElementsByClass(this.unwrappedElementContainer.mainDiv, "searchterm", "span"); | |
var wrappedEls = getElementsByClass(this.wrappedElementContainer.mainDiv, "searchterm", "span"); | |
for (i = 0, len = unwrappedEls.length; i < len; i++) { | |
matches[i] = new Match(this.level, null, unwrappedEls[i], wrappedEls[i]); | |
} | |
} else { | |
var els = getElementsByClass(this.mainElementContainer.mainDiv, "searchterm", "span"); | |
for (i = 0, len = els.length; i < len; i++) { | |
matches[i] = new Match(this.level, els[i]); | |
} | |
} | |
return matches; | |
}, | |
setSearchMatch: function(isMatch) { | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].setSearchMatch(isMatch); | |
} | |
}, | |
clearSearch: function() { | |
for (var i = 0, len = this.elementContainers.length; i < len; i++) { | |
this.elementContainers[i].clearSearch(); | |
} | |
}, | |
accept: function(visitor) { | |
visitor.visitLogEntry(this); | |
}, | |
serialize: function(items) { | |
items.push([LogItem.serializedItemKeys.LOG_ENTRY, this.level, this.formattedMessage]); | |
} | |
}); | |
/*----------------------------------------------------------------*/ | |
function LogItemVisitor() { | |
} | |
LogItemVisitor.prototype = { | |
visit: function(logItem) { | |
}, | |
visitParent: function(logItem) { | |
if (logItem.group) { | |
logItem.group.accept(this); | |
} | |
}, | |
visitChildren: function(logItem) { | |
for (var i = 0, len = logItem.children.length; i < len; i++) { | |
logItem.children[i].accept(this); | |
} | |
}, | |
visitLogEntry: function(logEntry) { | |
this.visit(logEntry); | |
}, | |
visitSeparator: function(separator) { | |
this.visit(separator); | |
}, | |
visitGroup: function(group) { | |
this.visit(group); | |
} | |
}; | |
/*----------------------------------------------------------------*/ | |
function GroupFlattener() { | |
this.logEntriesAndSeparators = []; | |
} | |
GroupFlattener.prototype = new LogItemVisitor(); | |
GroupFlattener.prototype.visitGroup = function(group) { | |
this.visitChildren(group); | |
}; | |
GroupFlattener.prototype.visitLogEntry = function(logEntry) { | |
this.logEntriesAndSeparators.push(logEntry); | |
}; | |
GroupFlattener.prototype.visitSeparator = function(separator) { | |
this.logEntriesAndSeparators.push(separator); | |
}; | |
/*----------------------------------------------------------------*/ | |
window.onload = function() { | |
// Sort out document.domain | |
if (location.search) { | |
var queryBits = unescape(location.search).substr(1).split("&"), nameValueBits; | |
for (var i = 0, len = queryBits.length; i < len; i++) { | |
nameValueBits = queryBits[i].split("="); | |
if (nameValueBits[0] == "log4javascript_domain") { | |
document.domain = nameValueBits[1]; | |
break; | |
} | |
} | |
} | |
// Create DOM objects | |
logMainContainer = $("log"); | |
if (isIePre7) { | |
addClass(logMainContainer, "oldIe"); | |
} | |
rootGroup = new Group("root", true); | |
rootGroup.render(); | |
currentGroup = rootGroup; | |
setCommandInputWidth(); | |
setLogContainerHeight(); | |
toggleLoggingEnabled(); | |
toggleSearchEnabled(); | |
toggleSearchFilter(); | |
toggleSearchHighlight(); | |
applyFilters(); | |
checkAllLevels(); | |
toggleWrap(); | |
toggleNewestAtTop(); | |
toggleScrollToLatest(); | |
renderQueuedLogItems(); | |
loaded = true; | |
$("command").value = ""; | |
$("command").autocomplete = "off"; | |
$("command").onkeydown = function(evt) { | |
evt = getEvent(evt); | |
if (evt.keyCode == 10 || evt.keyCode == 13) { // Return/Enter | |
evalCommandLine(); | |
stopPropagation(evt); | |
} else if (evt.keyCode == 27) { // Escape | |
this.value = ""; | |
this.focus(); | |
} else if (evt.keyCode == 38 && commandHistory.length > 0) { // Up | |
currentCommandIndex = Math.max(0, currentCommandIndex - 1); | |
this.value = commandHistory[currentCommandIndex]; | |
moveCaretToEnd(this); | |
} else if (evt.keyCode == 40 && commandHistory.length > 0) { // Down | |
currentCommandIndex = Math.min(commandHistory.length - 1, currentCommandIndex + 1); | |
this.value = commandHistory[currentCommandIndex]; | |
moveCaretToEnd(this); | |
} | |
}; | |
// Prevent the keypress moving the caret in Firefox | |
$("command").onkeypress = function(evt) { | |
evt = getEvent(evt); | |
if (evt.keyCode == 38 && commandHistory.length > 0 && evt.preventDefault) { // Up | |
evt.preventDefault(); | |
} | |
}; | |
// Prevent the keyup event blurring the input in Opera | |
$("command").onkeyup = function(evt) { | |
evt = getEvent(evt); | |
if (evt.keyCode == 27 && evt.preventDefault) { // Up | |
evt.preventDefault(); | |
this.focus(); | |
} | |
}; | |
// Add document keyboard shortcuts | |
document.onkeydown = function keyEventHandler(evt) { | |
evt = getEvent(evt); | |
switch (evt.keyCode) { | |
case 69: // Ctrl + shift + E: re-execute last command | |
if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) { | |
evalLastCommand(); | |
cancelKeyEvent(evt); | |
return false; | |
} | |
break; | |
case 75: // Ctrl + shift + K: focus search | |
if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) { | |
focusSearch(); | |
cancelKeyEvent(evt); | |
return false; | |
} | |
break; | |
case 40: // Ctrl + shift + down arrow: focus command line | |
case 76: // Ctrl + shift + L: focus command line | |
if (evt.shiftKey && (evt.ctrlKey || evt.metaKey)) { | |
focusCommandLine(); | |
cancelKeyEvent(evt); | |
return false; | |
} | |
break; | |
} | |
}; | |
// Workaround to make sure log div starts at the correct size | |
setTimeout(setLogContainerHeight, 20); | |
setShowCommandLine(showCommandLine); | |
doSearch(); | |
}; | |
window.onunload = function() { | |
if (mainWindowExists()) { | |
appender.unload(); | |
} | |
appender = null; | |
}; | |
/*----------------------------------------------------------------*/ | |
function toggleLoggingEnabled() { | |
setLoggingEnabled($("enableLogging").checked); | |
} | |
function setLoggingEnabled(enable) { | |
loggingEnabled = enable; | |
} | |
var appender = null; | |
function setAppender(appenderParam) { | |
appender = appenderParam; | |
} | |
function setShowCloseButton(showCloseButton) { | |
$("closeButton").style.display = showCloseButton ? "inline" : "none"; | |
} | |
function setShowHideButton(showHideButton) { | |
$("hideButton").style.display = showHideButton ? "inline" : "none"; | |
} | |
var newestAtTop = false; | |
/*----------------------------------------------------------------*/ | |
function LogItemContentReverser() { | |
} | |
LogItemContentReverser.prototype = new LogItemVisitor(); | |
LogItemContentReverser.prototype.visitGroup = function(group) { | |
group.reverseChildren(); | |
this.visitChildren(group); | |
}; | |
/*----------------------------------------------------------------*/ | |
function setNewestAtTop(isNewestAtTop) { | |
var oldNewestAtTop = newestAtTop; | |
var i, iLen, j, jLen; | |
newestAtTop = Boolean(isNewestAtTop); | |
if (oldNewestAtTop != newestAtTop) { | |
var visitor = new LogItemContentReverser(); | |
rootGroup.accept(visitor); | |
// Reassemble the matches array | |
if (currentSearch) { | |
var currentMatch = currentSearch.matches[currentMatchIndex]; | |
var matchIndex = 0; | |
var matches = []; | |
var actOnLogEntry = function(logEntry) { | |
var logEntryMatches = logEntry.getSearchMatches(); | |
for (j = 0, jLen = logEntryMatches.length; j < jLen; j++) { | |
matches[matchIndex] = logEntryMatches[j]; | |
if (currentMatch && logEntryMatches[j].equals(currentMatch)) { | |
currentMatchIndex = matchIndex; | |
} | |
matchIndex++; | |
} | |
}; | |
if (newestAtTop) { | |
for (i = logEntries.length - 1; i >= 0; i--) { | |
actOnLogEntry(logEntries[i]); | |
} | |
} else { | |
for (i = 0, iLen = logEntries.length; i < iLen; i++) { | |
actOnLogEntry(logEntries[i]); | |
} | |
} | |
currentSearch.matches = matches; | |
if (currentMatch) { | |
currentMatch.setCurrent(); | |
} | |
} else if (scrollToLatest) { | |
doScrollToLatest(); | |
} | |
} | |
$("newestAtTop").checked = isNewestAtTop; | |
} | |
function toggleNewestAtTop() { | |
var isNewestAtTop = $("newestAtTop").checked; | |
setNewestAtTop(isNewestAtTop); | |
} | |
var scrollToLatest = true; | |
function setScrollToLatest(isScrollToLatest) { | |
scrollToLatest = isScrollToLatest; | |
if (scrollToLatest) { | |
doScrollToLatest(); | |
} | |
$("scrollToLatest").checked = isScrollToLatest; | |
} | |
function toggleScrollToLatest() { | |
var isScrollToLatest = $("scrollToLatest").checked; | |
setScrollToLatest(isScrollToLatest); | |
} | |
function doScrollToLatest() { | |
var l = logMainContainer; | |
if (typeof l.scrollTop != "undefined") { | |
if (newestAtTop) { | |
l.scrollTop = 0; | |
} else { | |
var latestLogEntry = l.lastChild; | |
if (latestLogEntry) { | |
l.scrollTop = l.scrollHeight; | |
} | |
} | |
} | |
} | |
var closeIfOpenerCloses = true; | |
function setCloseIfOpenerCloses(isCloseIfOpenerCloses) { | |
closeIfOpenerCloses = isCloseIfOpenerCloses; | |
} | |
var maxMessages = null; | |
function setMaxMessages(max) { | |
maxMessages = max; | |
pruneLogEntries(); | |
} | |
var showCommandLine = false; | |
function setShowCommandLine(isShowCommandLine) { | |
showCommandLine = isShowCommandLine; | |
if (loaded) { | |
$("commandLine").style.display = showCommandLine ? "block" : "none"; | |
setCommandInputWidth(); | |
setLogContainerHeight(); | |
} | |
} | |
function focusCommandLine() { | |
if (loaded) { | |
$("command").focus(); | |
} | |
} | |
function focusSearch() { | |
if (loaded) { | |
$("searchBox").focus(); | |
} | |
} | |
function getLogItems() { | |
var items = []; | |
for (var i = 0, len = logItems.length; i < len; i++) { | |
logItems[i].serialize(items); | |
} | |
return items; | |
} | |
function setLogItems(items) { | |
var loggingReallyEnabled = loggingEnabled; | |
// Temporarily turn logging on | |
loggingEnabled = true; | |
for (var i = 0, len = items.length; i < len; i++) { | |
switch (items[i][0]) { | |
case LogItem.serializedItemKeys.LOG_ENTRY: | |
log(items[i][1], items[i][2]); | |
break; | |
case LogItem.serializedItemKeys.GROUP_START: | |
group(items[i][1]); | |
break; | |
case LogItem.serializedItemKeys.GROUP_END: | |
groupEnd(); | |
break; | |
} | |
} | |
loggingEnabled = loggingReallyEnabled; | |
} | |
function log(logLevel, formattedMessage) { | |
if (loggingEnabled) { | |
var logEntry = new LogEntry(logLevel, formattedMessage); | |
logEntries.push(logEntry); | |
logEntriesAndSeparators.push(logEntry); | |
logItems.push(logEntry); | |
currentGroup.addChild(logEntry); | |
if (loaded) { | |
if (logQueuedEventsTimer !== null) { | |
clearTimeout(logQueuedEventsTimer); | |
} | |
logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay); | |
unrenderedLogItemsExist = true; | |
} | |
} | |
} | |
function renderQueuedLogItems() { | |
logQueuedEventsTimer = null; | |
var pruned = pruneLogEntries(); | |
// Render any unrendered log entries and apply the current search to them | |
var initiallyHasMatches = currentSearch ? currentSearch.hasMatches() : false; | |
for (var i = 0, len = logItems.length; i < len; i++) { | |
if (!logItems[i].rendered) { | |
logItems[i].render(); | |
logItems[i].appendToLog(); | |
if (currentSearch && (logItems[i] instanceof LogEntry)) { | |
currentSearch.applyTo(logItems[i]); | |
} | |
} | |
} | |
if (currentSearch) { | |
if (pruned) { | |
if (currentSearch.hasVisibleMatches()) { | |
if (currentMatchIndex === null) { | |
setCurrentMatchIndex(0); | |
} | |
displayMatches(); | |
} else { | |
displayNoMatches(); | |
} | |
} else if (!initiallyHasMatches && currentSearch.hasVisibleMatches()) { | |
setCurrentMatchIndex(0); | |
displayMatches(); | |
} | |
} | |
if (scrollToLatest) { | |
doScrollToLatest(); | |
} | |
unrenderedLogItemsExist = false; | |
} | |
function pruneLogEntries() { | |
if ((maxMessages !== null) && (logEntriesAndSeparators.length > maxMessages)) { | |
var numberToDelete = logEntriesAndSeparators.length - maxMessages; | |
var prunedLogEntries = logEntriesAndSeparators.slice(0, numberToDelete); | |
if (currentSearch) { | |
currentSearch.removeMatches(prunedLogEntries); | |
} | |
var group; | |
for (var i = 0; i < numberToDelete; i++) { | |
group = logEntriesAndSeparators[i].group; | |
array_remove(logItems, logEntriesAndSeparators[i]); | |
array_remove(logEntries, logEntriesAndSeparators[i]); | |
logEntriesAndSeparators[i].remove(true, true); | |
if (group.children.length === 0 && group !== currentGroup && group !== rootGroup) { | |
array_remove(logItems, group); | |
group.remove(true, true); | |
} | |
} | |
logEntriesAndSeparators = array_removeFromStart(logEntriesAndSeparators, numberToDelete); | |
return true; | |
} | |
return false; | |
} | |
function group(name, startExpanded) { | |
if (loggingEnabled) { | |
initiallyExpanded = (typeof startExpanded === "undefined") ? true : Boolean(startExpanded); | |
var newGroup = new Group(name, false, initiallyExpanded); | |
currentGroup.addChild(newGroup); | |
currentGroup = newGroup; | |
logItems.push(newGroup); | |
if (loaded) { | |
if (logQueuedEventsTimer !== null) { | |
clearTimeout(logQueuedEventsTimer); | |
} | |
logQueuedEventsTimer = setTimeout(renderQueuedLogItems, renderDelay); | |
unrenderedLogItemsExist = true; | |
} | |
} | |
} | |
function groupEnd() { | |
currentGroup = (currentGroup === rootGroup) ? rootGroup : currentGroup.group; | |
} | |
function mainPageReloaded() { | |
currentGroup = rootGroup; | |
var separator = new Separator(); | |
logEntriesAndSeparators.push(separator); | |
logItems.push(separator); | |
currentGroup.addChild(separator); | |
} | |
function closeWindow() { | |
if (appender && mainWindowExists()) { | |
appender.close(true); | |
} else { | |
window.close(); | |
} | |
} | |
function hide() { | |
if (appender && mainWindowExists()) { | |
appender.hide(); | |
} | |
} | |
var mainWindow = window; | |
var windowId = "log4javascriptConsoleWindow_" + new Date().getTime() + "_" + ("" + Math.random()).substr(2); | |
function setMainWindow(win) { | |
mainWindow = win; | |
mainWindow[windowId] = window; | |
// If this is a pop-up, poll the opener to see if it's closed | |
if (opener && closeIfOpenerCloses) { | |
pollOpener(); | |
} | |
} | |
function pollOpener() { | |
if (closeIfOpenerCloses) { | |
if (mainWindowExists()) { | |
setTimeout(pollOpener, 500); | |
} else { | |
closeWindow(); | |
} | |
} | |
} | |
function mainWindowExists() { | |
try { | |
return (mainWindow && !mainWindow.closed && | |
mainWindow[windowId] == window); | |
} catch (ex) {} | |
return false; | |
} | |
var logLevels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"]; | |
function getCheckBox(logLevel) { | |
return $("switch_" + logLevel); | |
} | |
function getIeWrappedLogContainer() { | |
return $("log_wrapped"); | |
} | |
function getIeUnwrappedLogContainer() { | |
return $("log_unwrapped"); | |
} | |
function applyFilters() { | |
for (var i = 0; i < logLevels.length; i++) { | |
if (getCheckBox(logLevels[i]).checked) { | |
addClass(logMainContainer, logLevels[i]); | |
} else { | |
removeClass(logMainContainer, logLevels[i]); | |
} | |
} | |
updateSearchFromFilters(); | |
} | |
function toggleAllLevels() { | |
var turnOn = $("switch_ALL").checked; | |
for (var i = 0; i < logLevels.length; i++) { | |
getCheckBox(logLevels[i]).checked = turnOn; | |
if (turnOn) { | |
addClass(logMainContainer, logLevels[i]); | |
} else { | |
removeClass(logMainContainer, logLevels[i]); | |
} | |
} | |
} | |
function checkAllLevels() { | |
for (var i = 0; i < logLevels.length; i++) { | |
if (!getCheckBox(logLevels[i]).checked) { | |
getCheckBox("ALL").checked = false; | |
return; | |
} | |
} | |
getCheckBox("ALL").checked = true; | |
} | |
function clearLog() { | |
rootGroup.clear(); | |
currentGroup = rootGroup; | |
logEntries = []; | |
logItems = []; | |
logEntriesAndSeparators = []; | |
doSearch(); | |
} | |
function toggleWrap() { | |
var enable = $("wrap").checked; | |
if (enable) { | |
addClass(logMainContainer, "wrap"); | |
} else { | |
removeClass(logMainContainer, "wrap"); | |
} | |
refreshCurrentMatch(); | |
} | |
/* ------------------------------------------------------------------- */ | |
// Search | |
var searchTimer = null; | |
function scheduleSearch() { | |
try { | |
clearTimeout(searchTimer); | |
} catch (ex) { | |
// Do nothing | |
} | |
searchTimer = setTimeout(doSearch, 500); | |
} | |
function Search(searchTerm, isRegex, searchRegex, isCaseSensitive) { | |
this.searchTerm = searchTerm; | |
this.isRegex = isRegex; | |
this.searchRegex = searchRegex; | |
this.isCaseSensitive = isCaseSensitive; | |
this.matches = []; | |
} | |
Search.prototype = { | |
hasMatches: function() { | |
return this.matches.length > 0; | |
}, | |
hasVisibleMatches: function() { | |
if (this.hasMatches()) { | |
for (var i = 0; i < this.matches.length; i++) { | |
if (this.matches[i].isVisible()) { | |
return true; | |
} | |
} | |
} | |
return false; | |
}, | |
match: function(logEntry) { | |
var entryText = String(logEntry.formattedMessage); | |
var matchesSearch = false; | |
if (this.isRegex) { | |
matchesSearch = this.searchRegex.test(entryText); | |
} else if (this.isCaseSensitive) { | |
matchesSearch = (entryText.indexOf(this.searchTerm) > -1); | |
} else { | |
matchesSearch = (entryText.toLowerCase().indexOf(this.searchTerm.toLowerCase()) > -1); | |
} | |
return matchesSearch; | |
}, | |
getNextVisibleMatchIndex: function() { | |
for (var i = currentMatchIndex + 1; i < this.matches.length; i++) { | |
if (this.matches[i].isVisible()) { | |
return i; | |
} | |
} | |
// Start again from the first match | |
for (i = 0; i <= currentMatchIndex; i++) { | |
if (this.matches[i].isVisible()) { | |
return i; | |
} | |
} | |
return -1; | |
}, | |
getPreviousVisibleMatchIndex: function() { | |
for (var i = currentMatchIndex - 1; i >= 0; i--) { | |
if (this.matches[i].isVisible()) { | |
return i; | |
} | |
} | |
// Start again from the last match | |
for (var i = this.matches.length - 1; i >= currentMatchIndex; i--) { | |
if (this.matches[i].isVisible()) { | |
return i; | |
} | |
} | |
return -1; | |
}, | |
applyTo: function(logEntry) { | |
var doesMatch = this.match(logEntry); | |
if (doesMatch) { | |
logEntry.group.expand(); | |
logEntry.setSearchMatch(true); | |
var logEntryContent; | |
var wrappedLogEntryContent; | |
var searchTermReplacementStartTag = "<span class=\"searchterm\">"; | |
var searchTermReplacementEndTag = "<" + "/span>"; | |
var preTagName = isIe ? "pre" : "span"; | |
var preStartTag = "<" + preTagName + " class=\"pre\">"; | |
var preEndTag = "<" + "/" + preTagName + ">"; | |
var startIndex = 0; | |
var searchIndex, matchedText, textBeforeMatch; | |
if (this.isRegex) { | |
var flags = this.isCaseSensitive ? "g" : "gi"; | |
var capturingRegex = new RegExp("(" + this.searchRegex.source + ")", flags); | |
// Replace the search term with temporary tokens for the start and end tags | |
var rnd = ("" + Math.random()).substr(2); | |
var startToken = "%%s" + rnd + "%%"; | |
var endToken = "%%e" + rnd + "%%"; | |
logEntryContent = logEntry.formattedMessage.replace(capturingRegex, startToken + "$1" + endToken); | |
// Escape the HTML to get rid of angle brackets | |
logEntryContent = escapeHtml(logEntryContent); | |
// Substitute the proper HTML back in for the search match | |
var result; | |
var searchString = logEntryContent; | |
logEntryContent = ""; | |
wrappedLogEntryContent = ""; | |
while ((searchIndex = searchString.indexOf(startToken, startIndex)) > -1) { | |
var endTokenIndex = searchString.indexOf(endToken, searchIndex); | |
matchedText = searchString.substring(searchIndex + startToken.length, endTokenIndex); | |
textBeforeMatch = searchString.substring(startIndex, searchIndex); | |
logEntryContent += preStartTag + textBeforeMatch + preEndTag; | |
logEntryContent += searchTermReplacementStartTag + preStartTag + matchedText + | |
preEndTag + searchTermReplacementEndTag; | |
if (isIe) { | |
wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag + | |
matchedText + searchTermReplacementEndTag; | |
} | |
startIndex = endTokenIndex + endToken.length; | |
} | |
logEntryContent += preStartTag + searchString.substr(startIndex) + preEndTag; | |
if (isIe) { | |
wrappedLogEntryContent += searchString.substr(startIndex); | |
} | |
} else { | |
logEntryContent = ""; | |
wrappedLogEntryContent = ""; | |
var searchTermReplacementLength = searchTermReplacementStartTag.length + | |
this.searchTerm.length + searchTermReplacementEndTag.length; | |
var searchTermLength = this.searchTerm.length; | |
var searchTermLowerCase = this.searchTerm.toLowerCase(); | |
var logTextLowerCase = logEntry.formattedMessage.toLowerCase(); | |
while ((searchIndex = logTextLowerCase.indexOf(searchTermLowerCase, startIndex)) > -1) { | |
matchedText = escapeHtml(logEntry.formattedMessage.substr(searchIndex, this.searchTerm.length)); | |
textBeforeMatch = escapeHtml(logEntry.formattedMessage.substring(startIndex, searchIndex)); | |
var searchTermReplacement = searchTermReplacementStartTag + | |
preStartTag + matchedText + preEndTag + searchTermReplacementEndTag; | |
logEntryContent += preStartTag + textBeforeMatch + preEndTag + searchTermReplacement; | |
if (isIe) { | |
wrappedLogEntryContent += textBeforeMatch + searchTermReplacementStartTag + | |
matchedText + searchTermReplacementEndTag; | |
} | |
startIndex = searchIndex + searchTermLength; | |
} | |
var textAfterLastMatch = escapeHtml(logEntry.formattedMessage.substr(startIndex)); | |
logEntryContent += preStartTag + textAfterLastMatch + preEndTag; | |
if (isIe) { | |
wrappedLogEntryContent += textAfterLastMatch; | |
} | |
} | |
logEntry.setContent(logEntryContent, wrappedLogEntryContent); | |
var logEntryMatches = logEntry.getSearchMatches(); | |
this.matches = this.matches.concat(logEntryMatches); | |
} else { | |
logEntry.setSearchMatch(false); | |
logEntry.setContent(logEntry.formattedMessage, logEntry.formattedMessage); | |
} | |
return doesMatch; | |
}, | |
removeMatches: function(logEntries) { | |
var matchesToRemoveCount = 0; | |
var currentMatchRemoved = false; | |
var matchesToRemove = []; | |
var i, iLen, j, jLen; | |
// Establish the list of matches to be removed | |
for (i = 0, iLen = this.matches.length; i < iLen; i++) { | |
for (j = 0, jLen = logEntries.length; j < jLen; j++) { | |
if (this.matches[i].belongsTo(logEntries[j])) { | |
matchesToRemove.push(this.matches[i]); | |
if (i === currentMatchIndex) { | |
currentMatchRemoved = true; | |
} | |
} | |
} | |
} | |
// Set the new current match index if the current match has been deleted | |
// This will be the first match that appears after the first log entry being | |
// deleted, if one exists; otherwise, it's the first match overall | |
var newMatch = currentMatchRemoved ? null : this.matches[currentMatchIndex]; | |
if (currentMatchRemoved) { | |
for (i = currentMatchIndex, iLen = this.matches.length; i < iLen; i++) { | |
if (this.matches[i].isVisible() && !array_contains(matchesToRemove, this.matches[i])) { | |
newMatch = this.matches[i]; | |
break; | |
} | |
} | |
} | |
// Remove the matches | |
for (i = 0, iLen = matchesToRemove.length; i < iLen; i++) { | |
array_remove(this.matches, matchesToRemove[i]); | |
matchesToRemove[i].remove(); | |
} | |
// Set the new match, if one exists | |
if (this.hasVisibleMatches()) { | |
if (newMatch === null) { | |
setCurrentMatchIndex(0); | |
} else { | |
// Get the index of the new match | |
var newMatchIndex = 0; | |
for (i = 0, iLen = this.matches.length; i < iLen; i++) { | |
if (newMatch === this.matches[i]) { | |
newMatchIndex = i; | |
break; | |
} | |
} | |
setCurrentMatchIndex(newMatchIndex); | |
} | |
} else { | |
currentMatchIndex = null; | |
displayNoMatches(); | |
} | |
} | |
}; | |
function getPageOffsetTop(el, container) { | |
var currentEl = el; | |
var y = 0; | |
while (currentEl && currentEl != container) { | |
y += currentEl.offsetTop; | |
currentEl = currentEl.offsetParent; | |
} | |
return y; | |
} | |
function scrollIntoView(el) { | |
var logContainer = logMainContainer; | |
// Check if the whole width of the element is visible and centre if not | |
if (!$("wrap").checked) { | |
var logContainerLeft = logContainer.scrollLeft; | |
var logContainerRight = logContainerLeft + logContainer.offsetWidth; | |
var elLeft = el.offsetLeft; | |
var elRight = elLeft + el.offsetWidth; | |
if (elLeft < logContainerLeft || elRight > logContainerRight) { | |
logContainer.scrollLeft = elLeft - (logContainer.offsetWidth - el.offsetWidth) / 2; | |
} | |
} | |
// Check if the whole height of the element is visible and centre if not | |
var logContainerTop = logContainer.scrollTop; | |
var logContainerBottom = logContainerTop + logContainer.offsetHeight; | |
var elTop = getPageOffsetTop(el) - getToolBarsHeight(); | |
var elBottom = elTop + el.offsetHeight; | |
if (elTop < logContainerTop || elBottom > logContainerBottom) { | |
logContainer.scrollTop = elTop - (logContainer.offsetHeight - el.offsetHeight) / 2; | |
} | |
} | |
function Match(logEntryLevel, spanInMainDiv, spanInUnwrappedPre, spanInWrappedDiv) { | |
this.logEntryLevel = logEntryLevel; | |
this.spanInMainDiv = spanInMainDiv; | |
if (isIe) { | |
this.spanInUnwrappedPre = spanInUnwrappedPre; | |
this.spanInWrappedDiv = spanInWrappedDiv; | |
} | |
this.mainSpan = isIe ? spanInUnwrappedPre : spanInMainDiv; | |
} | |
Match.prototype = { | |
equals: function(match) { | |
return this.mainSpan === match.mainSpan; | |
}, | |
setCurrent: function() { | |
if (isIe) { | |
addClass(this.spanInUnwrappedPre, "currentmatch"); | |
addClass(this.spanInWrappedDiv, "currentmatch"); | |
// Scroll the visible one into view | |
var elementToScroll = $("wrap").checked ? this.spanInWrappedDiv : this.spanInUnwrappedPre; | |
scrollIntoView(elementToScroll); | |
} else { | |
addClass(this.spanInMainDiv, "currentmatch"); | |
scrollIntoView(this.spanInMainDiv); | |
} | |
}, | |
belongsTo: function(logEntry) { | |
if (isIe) { | |
return isDescendant(this.spanInUnwrappedPre, logEntry.unwrappedPre); | |
} else { | |
return isDescendant(this.spanInMainDiv, logEntry.mainDiv); | |
} | |
}, | |
setNotCurrent: function() { | |
if (isIe) { | |
removeClass(this.spanInUnwrappedPre, "currentmatch"); | |
removeClass(this.spanInWrappedDiv, "currentmatch"); | |
} else { | |
removeClass(this.spanInMainDiv, "currentmatch"); | |
} | |
}, | |
isOrphan: function() { | |
return isOrphan(this.mainSpan); | |
}, | |
isVisible: function() { | |
return getCheckBox(this.logEntryLevel).checked; | |
}, | |
remove: function() { | |
if (isIe) { | |
this.spanInUnwrappedPre = null; | |
this.spanInWrappedDiv = null; | |
} else { | |
this.spanInMainDiv = null; | |
} | |
} | |
}; | |
var currentSearch = null; | |
var currentMatchIndex = null; | |
function doSearch() { | |
var searchBox = $("searchBox"); | |
var searchTerm = searchBox.value; | |
var isRegex = $("searchRegex").checked; | |
var isCaseSensitive = $("searchCaseSensitive").checked; | |
var i; | |
if (searchTerm === "") { | |
$("searchReset").disabled = true; | |
$("searchNav").style.display = "none"; | |
removeClass(document.body, "searching"); | |
removeClass(searchBox, "hasmatches"); | |
removeClass(searchBox, "nomatches"); | |
for (i = 0; i < logEntries.length; i++) { | |
logEntries[i].clearSearch(); | |
logEntries[i].setContent(logEntries[i].formattedMessage, logEntries[i].formattedMessage); | |
} | |
currentSearch = null; | |
setLogContainerHeight(); | |
} else { | |
$("searchReset").disabled = false; | |
$("searchNav").style.display = "block"; | |
var searchRegex; | |
var regexValid; | |
if (isRegex) { | |
try { | |
searchRegex = isCaseSensitive ? new RegExp(searchTerm, "g") : new RegExp(searchTerm, "gi"); | |
regexValid = true; | |
replaceClass(searchBox, "validregex", "invalidregex"); | |
searchBox.title = "Valid regex"; | |
} catch (ex) { | |
regexValid = false; | |
replaceClass(searchBox, "invalidregex", "validregex"); | |
searchBox.title = "Invalid regex: " + (ex.message ? ex.message : (ex.description ? ex.description : "unknown error")); | |
return; | |
} | |
} else { | |
searchBox.title = ""; | |
removeClass(searchBox, "validregex"); | |
removeClass(searchBox, "invalidregex"); | |
} | |
addClass(document.body, "searching"); | |
currentSearch = new Search(searchTerm, isRegex, searchRegex, isCaseSensitive); | |
for (i = 0; i < logEntries.length; i++) { | |
currentSearch.applyTo(logEntries[i]); | |
} | |
setLogContainerHeight(); | |
// Highlight the first search match | |
if (currentSearch.hasVisibleMatches()) { | |
setCurrentMatchIndex(0); | |
displayMatches(); | |
} else { | |
displayNoMatches(); | |
} | |
} | |
} | |
function updateSearchFromFilters() { | |
if (currentSearch) { | |
if (currentSearch.hasMatches()) { | |
if (currentMatchIndex === null) { | |
currentMatchIndex = 0; | |
} | |
var currentMatch = currentSearch.matches[currentMatchIndex]; | |
if (currentMatch.isVisible()) { | |
displayMatches(); | |
setCurrentMatchIndex(currentMatchIndex); | |
} else { | |
currentMatch.setNotCurrent(); | |
// Find the next visible match, if one exists | |
var nextVisibleMatchIndex = currentSearch.getNextVisibleMatchIndex(); | |
if (nextVisibleMatchIndex > -1) { | |
setCurrentMatchIndex(nextVisibleMatchIndex); | |
displayMatches(); | |
} else { | |
displayNoMatches(); | |
} | |
} | |
} else { | |
displayNoMatches(); | |
} | |
} | |
} | |
function refreshCurrentMatch() { | |
if (currentSearch && currentSearch.hasVisibleMatches()) { | |
setCurrentMatchIndex(currentMatchIndex); | |
} | |
} | |
function displayMatches() { | |
replaceClass($("searchBox"), "hasmatches", "nomatches"); | |
$("searchBox").title = "" + currentSearch.matches.length + " matches found"; | |
$("searchNav").style.display = "block"; | |
setLogContainerHeight(); | |
} | |
function displayNoMatches() { | |
replaceClass($("searchBox"), "nomatches", "hasmatches"); | |
$("searchBox").title = "No matches found"; | |
$("searchNav").style.display = "none"; | |
setLogContainerHeight(); | |
} | |
function toggleSearchEnabled(enable) { | |
enable = (typeof enable == "undefined") ? !$("searchDisable").checked : enable; | |
$("searchBox").disabled = !enable; | |
$("searchReset").disabled = !enable; | |
$("searchRegex").disabled = !enable; | |
$("searchNext").disabled = !enable; | |
$("searchPrevious").disabled = !enable; | |
$("searchCaseSensitive").disabled = !enable; | |
$("searchNav").style.display = (enable && ($("searchBox").value !== "") && | |
currentSearch && currentSearch.hasVisibleMatches()) ? | |
"block" : "none"; | |
if (enable) { | |
removeClass($("search"), "greyedout"); | |
addClass(document.body, "searching"); | |
if ($("searchHighlight").checked) { | |
addClass(logMainContainer, "searchhighlight"); | |
} else { | |
removeClass(logMainContainer, "searchhighlight"); | |
} | |
if ($("searchFilter").checked) { | |
addClass(logMainContainer, "searchfilter"); | |
} else { | |
removeClass(logMainContainer, "searchfilter"); | |
} | |
$("searchDisable").checked = !enable; | |
} else { | |
addClass($("search"), "greyedout"); | |
removeClass(document.body, "searching"); | |
removeClass(logMainContainer, "searchhighlight"); | |
removeClass(logMainContainer, "searchfilter"); | |
} | |
setLogContainerHeight(); | |
} | |
function toggleSearchFilter() { | |
var enable = $("searchFilter").checked; | |
if (enable) { | |
addClass(logMainContainer, "searchfilter"); | |
} else { | |
removeClass(logMainContainer, "searchfilter"); | |
} | |
refreshCurrentMatch(); | |
} | |
function toggleSearchHighlight() { | |
var enable = $("searchHighlight").checked; | |
if (enable) { | |
addClass(logMainContainer, "searchhighlight"); | |
} else { | |
removeClass(logMainContainer, "searchhighlight"); | |
} | |
} | |
function clearSearch() { | |
$("searchBox").value = ""; | |
doSearch(); | |
} | |
function searchNext() { | |
if (currentSearch !== null && currentMatchIndex !== null) { | |
currentSearch.matches[currentMatchIndex].setNotCurrent(); | |
var nextMatchIndex = currentSearch.getNextVisibleMatchIndex(); | |
if (nextMatchIndex > currentMatchIndex || confirm("Reached the end of the page. Start from the top?")) { | |
setCurrentMatchIndex(nextMatchIndex); | |
} | |
} | |
} | |
function searchPrevious() { | |
if (currentSearch !== null && currentMatchIndex !== null) { | |
currentSearch.matches[currentMatchIndex].setNotCurrent(); | |
var previousMatchIndex = currentSearch.getPreviousVisibleMatchIndex(); | |
if (previousMatchIndex < currentMatchIndex || confirm("Reached the start of the page. Continue from the bottom?")) { | |
setCurrentMatchIndex(previousMatchIndex); | |
} | |
} | |
} | |
function setCurrentMatchIndex(index) { | |
currentMatchIndex = index; | |
currentSearch.matches[currentMatchIndex].setCurrent(); | |
} | |
/* ------------------------------------------------------------------------- */ | |
// CSS Utilities | |
function addClass(el, cssClass) { | |
if (!hasClass(el, cssClass)) { | |
if (el.className) { | |
el.className += " " + cssClass; | |
} else { | |
el.className = cssClass; | |
} | |
} | |
} | |
function hasClass(el, cssClass) { | |
if (el.className) { | |
var classNames = el.className.split(" "); | |
return array_contains(classNames, cssClass); | |
} | |
return false; | |
} | |
function removeClass(el, cssClass) { | |
if (hasClass(el, cssClass)) { | |
// Rebuild the className property | |
var existingClasses = el.className.split(" "); | |
var newClasses = []; | |
for (var i = 0, len = existingClasses.length; i < len; i++) { | |
if (existingClasses[i] != cssClass) { | |
newClasses[newClasses.length] = existingClasses[i]; | |
} | |
} | |
el.className = newClasses.join(" "); | |
} | |
} | |
function replaceClass(el, newCssClass, oldCssClass) { | |
removeClass(el, oldCssClass); | |
addClass(el, newCssClass); | |
} | |
/* ------------------------------------------------------------------------- */ | |
// Other utility functions | |
function getElementsByClass(el, cssClass, tagName) { | |
var elements = el.getElementsByTagName(tagName); | |
var matches = []; | |
for (var i = 0, len = elements.length; i < len; i++) { | |
if (hasClass(elements[i], cssClass)) { | |
matches.push(elements[i]); | |
} | |
} | |
return matches; | |
} | |
// Syntax borrowed from Prototype library | |
function $(id) { | |
return document.getElementById(id); | |
} | |
function isDescendant(node, ancestorNode) { | |
while (node != null) { | |
if (node === ancestorNode) { | |
return true; | |
} | |
node = node.parentNode; | |
} | |
return false; | |
} | |
function isOrphan(node) { | |
var currentNode = node; | |
while (currentNode) { | |
if (currentNode == document.body) { | |
return false; | |
} | |
currentNode = currentNode.parentNode; | |
} | |
return true; | |
} | |
function escapeHtml(str) { | |
return str.replace(/&/g, "&").replace(/[<]/g, "<").replace(/>/g, ">"); | |
} | |
function getWindowWidth() { | |
if (window.innerWidth) { | |
return window.innerWidth; | |
} else if (document.documentElement && document.documentElement.clientWidth) { | |
return document.documentElement.clientWidth; | |
} else if (document.body) { | |
return document.body.clientWidth; | |
} | |
return 0; | |
} | |
function getWindowHeight() { | |
if (window.innerHeight) { | |
return window.innerHeight; | |
} else if (document.documentElement && document.documentElement.clientHeight) { | |
return document.documentElement.clientHeight; | |
} else if (document.body) { | |
return document.body.clientHeight; | |
} | |
return 0; | |
} | |
function getToolBarsHeight() { | |
return $("switches").offsetHeight; | |
} | |
function getChromeHeight() { | |
var height = getToolBarsHeight(); | |
if (showCommandLine) { | |
height += $("commandLine").offsetHeight; | |
} | |
return height; | |
} | |
function setLogContainerHeight() { | |
if (logMainContainer) { | |
var windowHeight = getWindowHeight(); | |
$("body").style.height = getWindowHeight() + "px"; | |
logMainContainer.style.height = "" + | |
Math.max(0, windowHeight - getChromeHeight()) + "px"; | |
} | |
} | |
function setCommandInputWidth() { | |
if (showCommandLine) { | |
$("command").style.width = "" + Math.max(0, $("commandLineContainer").offsetWidth - | |
($("evaluateButton").offsetWidth + 13)) + "px"; | |
} | |
} | |
window.onresize = function() { | |
setCommandInputWidth(); | |
setLogContainerHeight(); | |
}; | |
if (!Array.prototype.push) { | |
Array.prototype.push = function() { | |
for (var i = 0, len = arguments.length; i < len; i++){ | |
this[this.length] = arguments[i]; | |
} | |
return this.length; | |
}; | |
} | |
if (!Array.prototype.pop) { | |
Array.prototype.pop = function() { | |
if (this.length > 0) { | |
var val = this[this.length - 1]; | |
this.length = this.length - 1; | |
return val; | |
} | |
}; | |
} | |
if (!Array.prototype.shift) { | |
Array.prototype.shift = function() { | |
if (this.length > 0) { | |
var firstItem = this[0]; | |
for (var i = 0, len = this.length - 1; i < len; i++) { | |
this[i] = this[i + 1]; | |
} | |
this.length = this.length - 1; | |
return firstItem; | |
} | |
}; | |
} | |
if (!Array.prototype.splice) { | |
Array.prototype.splice = function(startIndex, deleteCount) { | |
var itemsAfterDeleted = this.slice(startIndex + deleteCount); | |
var itemsDeleted = this.slice(startIndex, startIndex + deleteCount); | |
this.length = startIndex; | |
// Copy the arguments into a proper Array object | |
var argumentsArray = []; | |
for (var i = 0, len = arguments.length; i < len; i++) { | |
argumentsArray[i] = arguments[i]; | |
} | |
var itemsToAppend = (argumentsArray.length > 2) ? | |
itemsAfterDeleted = argumentsArray.slice(2).concat(itemsAfterDeleted) : itemsAfterDeleted; | |
for (i = 0, len = itemsToAppend.length; i < len; i++) { | |
this.push(itemsToAppend[i]); | |
} | |
return itemsDeleted; | |
}; | |
} | |
function array_remove(arr, val) { | |
var index = -1; | |
for (var i = 0, len = arr.length; i < len; i++) { | |
if (arr[i] === val) { | |
index = i; | |
break; | |
} | |
} | |
if (index >= 0) { | |
arr.splice(index, 1); | |
return index; | |
} else { | |
return false; | |
} | |
} | |
function array_removeFromStart(array, numberToRemove) { | |
if (Array.prototype.splice) { | |
array.splice(0, numberToRemove); | |
} else { | |
for (var i = numberToRemove, len = array.length; i < len; i++) { | |
array[i - numberToRemove] = array[i]; | |
} | |
array.length = array.length - numberToRemove; | |
} | |
return array; | |
} | |
function array_contains(arr, val) { | |
for (var i = 0, len = arr.length; i < len; i++) { | |
if (arr[i] == val) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function getErrorMessage(ex) { | |
if (ex.message) { | |
return ex.message; | |
} else if (ex.description) { | |
return ex.description; | |
} | |
return "" + ex; | |
} | |
function moveCaretToEnd(input) { | |
if (input.setSelectionRange) { | |
input.focus(); | |
var length = input.value.length; | |
input.setSelectionRange(length, length); | |
} else if (input.createTextRange) { | |
var range = input.createTextRange(); | |
range.collapse(false); | |
range.select(); | |
} | |
input.focus(); | |
} | |
function stopPropagation(evt) { | |
if (evt.stopPropagation) { | |
evt.stopPropagation(); | |
} else if (typeof evt.cancelBubble != "undefined") { | |
evt.cancelBubble = true; | |
} | |
} | |
function getEvent(evt) { | |
return evt ? evt : event; | |
} | |
function getTarget(evt) { | |
return evt.target ? evt.target : evt.srcElement; | |
} | |
function getRelatedTarget(evt) { | |
if (evt.relatedTarget) { | |
return evt.relatedTarget; | |
} else if (evt.srcElement) { | |
switch(evt.type) { | |
case "mouseover": | |
return evt.fromElement; | |
case "mouseout": | |
return evt.toElement; | |
default: | |
return evt.srcElement; | |
} | |
} | |
} | |
function cancelKeyEvent(evt) { | |
evt.returnValue = false; | |
stopPropagation(evt); | |
} | |
function evalCommandLine() { | |
var expr = $("command").value; | |
evalCommand(expr); | |
$("command").value = ""; | |
} | |
function evalLastCommand() { | |
if (lastCommand != null) { | |
evalCommand(lastCommand); | |
} | |
} | |
var lastCommand = null; | |
var commandHistory = []; | |
var currentCommandIndex = 0; | |
function evalCommand(expr) { | |
if (appender) { | |
appender.evalCommandAndAppend(expr); | |
} else { | |
var prefix = ">>> " + expr + "\r\n"; | |
try { | |
log("INFO", prefix + eval(expr)); | |
} catch (ex) { | |
log("ERROR", prefix + "Error: " + getErrorMessage(ex)); | |
} | |
} | |
// Update command history | |
if (expr != commandHistory[commandHistory.length - 1]) { | |
commandHistory.push(expr); | |
// Update the appender | |
if (appender) { | |
appender.storeCommandHistory(commandHistory); | |
} | |
} | |
currentCommandIndex = (expr == commandHistory[currentCommandIndex]) ? currentCommandIndex + 1 : commandHistory.length; | |
lastCommand = expr; | |
} | |
//]]> | |
</script> | |
<style type="text/css"> | |
body { | |
background-color: white; | |
color: black; | |
padding: 0; | |
margin: 0; | |
font-family: tahoma, verdana, arial, helvetica, sans-serif; | |
overflow: hidden; | |
} | |
div#switchesContainer input { | |
margin-bottom: 0; | |
} | |
div.toolbar { | |
border-top: solid #ffffff 1px; | |
border-bottom: solid #aca899 1px; | |
background-color: #f1efe7; | |
padding: 3px 5px; | |
font-size: 68.75%; | |
} | |
div.toolbar, div#search input { | |
font-family: tahoma, verdana, arial, helvetica, sans-serif; | |
} | |
div.toolbar input.button { | |
padding: 0 5px; | |
font-size: 100%; | |
} | |
div.toolbar input.hidden { | |
display: none; | |
} | |
div#switches input#clearButton { | |
margin-left: 20px; | |
} | |
div#levels label { | |
font-weight: bold; | |
} | |
div#levels label, div#options label { | |
margin-right: 5px; | |
} | |
div#levels label#wrapLabel { | |
font-weight: normal; | |
} | |
div#search label { | |
margin-right: 10px; | |
} | |
div#search label.searchboxlabel { | |
margin-right: 0; | |
} | |
div#search input { | |
font-size: 100%; | |
} | |
div#search input.validregex { | |
color: green; | |
} | |
div#search input.invalidregex { | |
color: red; | |
} | |
div#search input.nomatches { | |
color: white; | |
background-color: #ff6666; | |
} | |
div#search input.nomatches { | |
color: white; | |
background-color: #ff6666; | |
} | |
div#searchNav { | |
display: none; | |
} | |
div#commandLine { | |
display: none; | |
} | |
div#commandLine input#command { | |
font-size: 100%; | |
font-family: Courier New, Courier; | |
} | |
div#commandLine input#evaluateButton { | |
} | |
*.greyedout { | |
color: gray !important; | |
border-color: gray !important; | |
} | |
*.greyedout *.alwaysenabled { color: black; } | |
*.unselectable { | |
-khtml-user-select: none; | |
-moz-user-select: none; | |
user-select: none; | |
} | |
div#log { | |
font-family: Courier New, Courier; | |
font-size: 75%; | |
width: 100%; | |
overflow: auto; | |
clear: both; | |
position: relative; | |
} | |
div.group { | |
border-color: #cccccc; | |
border-style: solid; | |
border-width: 1px 0 1px 1px; | |
overflow: visible; | |
} | |
div.oldIe div.group, div.oldIe div.group *, div.oldIe *.logentry { | |
height: 1%; | |
} | |
div.group div.groupheading span.expander { | |
border: solid black 1px; | |
font-family: Courier New, Courier; | |
font-size: 0.833em; | |
background-color: #eeeeee; | |
position: relative; | |
top: -1px; | |
color: black; | |
padding: 0 2px; | |
cursor: pointer; | |
cursor: hand; | |
height: 1%; | |
} | |
div.group div.groupcontent { | |
margin-left: 10px; | |
padding-bottom: 2px; | |
overflow: visible; | |
} | |
div.group div.expanded { | |
display: block; | |
} | |
div.group div.collapsed { | |
display: none; | |
} | |
*.logentry { | |
overflow: visible; | |
display: none; | |
white-space: pre; | |
} | |
span.pre { | |
white-space: pre; | |
} | |
pre.unwrapped { | |
display: inline !important; | |
} | |
pre.unwrapped pre.pre, div.wrapped pre.pre { | |
display: inline; | |
} | |
div.wrapped pre.pre { | |
white-space: normal; | |
} | |
div.wrapped { | |
display: none; | |
} | |
body.searching *.logentry span.currentmatch { | |
color: white !important; | |
background-color: green !important; | |
} | |
body.searching div.searchhighlight *.logentry span.searchterm { | |
color: black; | |
background-color: yellow; | |
} | |
div.wrap *.logentry { | |
white-space: normal !important; | |
border-width: 0 0 1px 0; | |
border-color: #dddddd; | |
border-style: dotted; | |
} | |
div.wrap #log_wrapped, #log_unwrapped { | |
display: block; | |
} | |
div.wrap #log_unwrapped, #log_wrapped { | |
display: none; | |
} | |
div.wrap *.logentry span.pre { | |
overflow: visible; | |
white-space: normal; | |
} | |
div.wrap *.logentry pre.unwrapped { | |
display: none; | |
} | |
div.wrap *.logentry span.wrapped { | |
display: inline; | |
} | |
div.searchfilter *.searchnonmatch { | |
display: none !important; | |
} | |
div#log *.TRACE, label#label_TRACE { | |
color: #666666; | |
} | |
div#log *.DEBUG, label#label_DEBUG { | |
color: green; | |
} | |
div#log *.INFO, label#label_INFO { | |
color: #000099; | |
} | |
div#log *.WARN, label#label_WARN { | |
color: #999900; | |
} | |
div#log *.ERROR, label#label_ERROR { | |
color: red; | |
} | |
div#log *.FATAL, label#label_FATAL { | |
color: #660066; | |
} | |
div.TRACE#log *.TRACE, | |
div.DEBUG#log *.DEBUG, | |
div.INFO#log *.INFO, | |
div.WARN#log *.WARN, | |
div.ERROR#log *.ERROR, | |
div.FATAL#log *.FATAL { | |
display: block; | |
} | |
div#log div.separator { | |
background-color: #cccccc; | |
margin: 5px 0; | |
line-height: 1px; | |
} | |
</style> | |
</head> | |
<body id="body"> | |
<div id="switchesContainer"> | |
<div id="switches"> | |
<div id="levels" class="toolbar"> | |
Filters: | |
<input type="checkbox" id="switch_TRACE" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide trace messages" /><label for="switch_TRACE" id="label_TRACE">trace</label> | |
<input type="checkbox" id="switch_DEBUG" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide debug messages" /><label for="switch_DEBUG" id="label_DEBUG">debug</label> | |
<input type="checkbox" id="switch_INFO" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide info messages" /><label for="switch_INFO" id="label_INFO">info</label> | |
<input type="checkbox" id="switch_WARN" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide warn messages" /><label for="switch_WARN" id="label_WARN">warn</label> | |
<input type="checkbox" id="switch_ERROR" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide error messages" /><label for="switch_ERROR" id="label_ERROR">error</label> | |
<input type="checkbox" id="switch_FATAL" onclick="applyFilters(); checkAllLevels()" checked="checked" title="Show/hide fatal messages" /><label for="switch_FATAL" id="label_FATAL">fatal</label> | |
<input type="checkbox" id="switch_ALL" onclick="toggleAllLevels(); applyFilters()" checked="checked" title="Show/hide all messages" /><label for="switch_ALL" id="label_ALL">all</label> | |
</div> | |
<div id="search" class="toolbar"> | |
<label for="searchBox" class="searchboxlabel">Search:</label> <input type="text" id="searchBox" onclick="toggleSearchEnabled(true)" onkeyup="scheduleSearch()" size="20" /> | |
<input type="button" id="searchReset" disabled="disabled" value="Reset" onclick="clearSearch()" class="button" title="Reset the search" /> | |
<input type="checkbox" id="searchRegex" onclick="doSearch()" title="If checked, search is treated as a regular expression" /><label for="searchRegex">Regex</label> | |
<input type="checkbox" id="searchCaseSensitive" onclick="doSearch()" title="If checked, search is case sensitive" /><label for="searchCaseSensitive">Match case</label> | |
<input type="checkbox" id="searchDisable" onclick="toggleSearchEnabled()" title="Enable/disable search" /><label for="searchDisable" class="alwaysenabled">Disable</label> | |
<div id="searchNav"> | |
<input type="button" id="searchNext" disabled="disabled" value="Next" onclick="searchNext()" class="button" title="Go to the next matching log entry" /> | |
<input type="button" id="searchPrevious" disabled="disabled" value="Previous" onclick="searchPrevious()" class="button" title="Go to the previous matching log entry" /> | |
<input type="checkbox" id="searchFilter" onclick="toggleSearchFilter()" title="If checked, non-matching log entries are filtered out" /><label for="searchFilter">Filter</label> | |
<input type="checkbox" id="searchHighlight" onclick="toggleSearchHighlight()" title="Highlight matched search terms" /><label for="searchHighlight" class="alwaysenabled">Highlight all</label> | |
</div> | |
</div> | |
<div id="options" class="toolbar"> | |
Options: | |
<input type="checkbox" id="enableLogging" onclick="toggleLoggingEnabled()" checked="checked" title="Enable/disable logging" /><label for="enableLogging" id="enableLoggingLabel">Log</label> | |
<input type="checkbox" id="wrap" onclick="toggleWrap()" title="Enable / disable word wrap" /><label for="wrap" id="wrapLabel">Wrap</label> | |
<input type="checkbox" id="newestAtTop" onclick="toggleNewestAtTop()" title="If checked, causes newest messages to appear at the top" /><label for="newestAtTop" id="newestAtTopLabel">Newest at the top</label> | |
<input type="checkbox" id="scrollToLatest" onclick="toggleScrollToLatest()" checked="checked" title="If checked, window automatically scrolls to a new message when it is added" /><label for="scrollToLatest" id="scrollToLatestLabel">Scroll to latest</label> | |
<input type="button" id="clearButton" value="Clear" onclick="clearLog()" class="button" title="Clear all log messages" /> | |
<input type="button" id="hideButton" value="Hide" onclick="hide()" class="hidden button" title="Hide the console" /> | |
<input type="button" id="closeButton" value="Close" onclick="closeWindow()" class="hidden button" title="Close the window" /> | |
</div> | |
</div> | |
</div> | |
<div id="log" class="TRACE DEBUG INFO WARN ERROR FATAL"></div> | |
<div id="commandLine" class="toolbar"> | |
<div id="commandLineContainer"> | |
<input type="text" id="command" title="Enter a JavaScript command here and hit return or press 'Evaluate'" /> | |
<input type="button" id="evaluateButton" value="Evaluate" class="button" title="Evaluate the command" onclick="evalCommandLine()" /> | |
</div> | |
</div> | |
</body> | |
</html> |