1 |
/* |
2 |
|
3 |
Created By: Corey Johnson |
4 |
E-mail: probablyCorey@gmail.com |
5 |
|
6 |
Requires: Prototype Javascript library (http://prototype.conio.net/) |
7 |
|
8 |
Use it all you want. Just remember to give me some credit :) |
9 |
|
10 |
*/ |
11 |
|
12 |
// ------------ |
13 |
// Custom Event |
14 |
// ------------ |
15 |
|
16 |
var CustomEvent = Class.create() |
17 |
CustomEvent.prototype = { |
18 |
initialize : function() { |
19 |
this.listeners = [] |
20 |
}, |
21 |
|
22 |
addListener : function(method) { |
23 |
this.listeners.push(method) |
24 |
}, |
25 |
|
26 |
removeListener : function(method) { |
27 |
var foundIndexes = this._findListenerIndexes(method) |
28 |
|
29 |
for(var i = 0; i < foundIndexes.length; i++) { |
30 |
this.listeners.splice(foundIndexes[i], 1) |
31 |
} |
32 |
}, |
33 |
|
34 |
dispatch : function(handler) { |
35 |
for(var i = 0; i < this.listeners.length; i++) { |
36 |
try { |
37 |
this.listeners[i](handler) |
38 |
} |
39 |
catch (e) { |
40 |
alert("Could not run the listener " + this.listeners[i] + ". " + e) |
41 |
} |
42 |
} |
43 |
}, |
44 |
|
45 |
// Private Methods |
46 |
// --------------- |
47 |
_findListenerIndexes : function(method) { |
48 |
var indexes = [] |
49 |
for(var i = 0; i < this.listeners.length; i++) { |
50 |
if (this.listeners[i] == method) { |
51 |
indexes.push(i) |
52 |
} |
53 |
} |
54 |
|
55 |
return indexes |
56 |
} |
57 |
} |
58 |
|
59 |
// ------ |
60 |
// Cookie |
61 |
// ------ |
62 |
|
63 |
var Cookie = { |
64 |
set : function(name, value, expirationInDays, path) { |
65 |
var cookie = escape(name) + "=" + escape(value) |
66 |
|
67 |
if (expirationInDays) { |
68 |
var date = new Date() |
69 |
date.setDate(date.getDate() + expirationInDays) |
70 |
cookie += "; expires=" + date.toGMTString() |
71 |
} |
72 |
|
73 |
if (path) { |
74 |
cookie += ";path=" + path |
75 |
} |
76 |
|
77 |
document.cookie = cookie |
78 |
|
79 |
if (value && (expirationInDays == undefined || expirationInDays > 0) && !this.get(name)) { |
80 |
Logger.error("Cookie (" + name + ") was not set correctly... The value was " + value.toString().length + " charachters long (This may be over the cookie limit)"); |
81 |
} |
82 |
}, |
83 |
|
84 |
get : function(name) { |
85 |
var pattern = "(^|;)\\s*" + escape(name) + "=([^;]+)" |
86 |
|
87 |
var m = document.cookie.match(pattern) |
88 |
if (m && m[2]) { |
89 |
return unescape(m[2]) |
90 |
} |
91 |
else return null |
92 |
}, |
93 |
|
94 |
getAll : function() { |
95 |
var cookies = document.cookie.split(';') |
96 |
var cookieArray = [] |
97 |
|
98 |
for (var i = 0; i < cookies.length; i++) { |
99 |
try { |
100 |
var name = unescape(cookies[i].match(/^\s*([^=]+)/m)[1]) |
101 |
var value = unescape(cookies[i].match(/=(.*$)/m)[1]) |
102 |
} |
103 |
catch (e) { |
104 |
continue |
105 |
} |
106 |
|
107 |
cookieArray.push({name : name, value : value}) |
108 |
|
109 |
if (cookieArray[name] != undefined) { |
110 |
Logger.waring("Trying to retrieve cookie named(" + name + "). There appears to be another property with this name though."); |
111 |
} |
112 |
|
113 |
cookieArray[name] = value |
114 |
} |
115 |
|
116 |
return cookieArray |
117 |
}, |
118 |
|
119 |
clear : function(name) { |
120 |
this.set(name, "", -1) |
121 |
}, |
122 |
|
123 |
clearAll : function() { |
124 |
var cookies = this.getAll() |
125 |
|
126 |
for(var i = 0; i < cookies.length; i++) { |
127 |
this.clear(cookies[i].name) |
128 |
} |
129 |
|
130 |
} |
131 |
} |
132 |
|
133 |
// ------ |
134 |
// Logger |
135 |
// ----- |
136 |
|
137 |
var Logger = { |
138 |
logEntries : [], |
139 |
|
140 |
onupdate : new CustomEvent(), |
141 |
onclear : new CustomEvent(), |
142 |
|
143 |
|
144 |
// Logger output |
145 |
log : function(message, tag) { |
146 |
var logEntry = new LogEntry(message, tag || "info") |
147 |
this.logEntries.push(logEntry) |
148 |
this.onupdate.dispatch(logEntry) |
149 |
}, |
150 |
|
151 |
info : function(message) { |
152 |
this.log(message, 'info') |
153 |
}, |
154 |
|
155 |
debug : function(message) { |
156 |
this.log(message, 'debug') |
157 |
}, |
158 |
|
159 |
warn : function(message) { |
160 |
this.log(message, 'warning') |
161 |
}, |
162 |
|
163 |
error : function(message, error) { |
164 |
this.log(message + ": \n" + error, 'error') |
165 |
}, |
166 |
|
167 |
clear : function () { |
168 |
this.logEntries = [] |
169 |
this.onclear.dispatch() |
170 |
} |
171 |
} |
172 |
|
173 |
var LogEntry = Class.create() |
174 |
LogEntry.prototype = { |
175 |
initialize : function(message, tag) { |
176 |
this.message = message |
177 |
this.tag = tag |
178 |
} |
179 |
} |
180 |
|
181 |
var LogConsole = Class.create() |
182 |
LogConsole.prototype = { |
183 |
|
184 |
// Properties |
185 |
// ---------- |
186 |
commandHistory : [], |
187 |
commandIndex : 0, |
188 |
|
189 |
// Methods |
190 |
// ------- |
191 |
|
192 |
initialize : function() { |
193 |
this.outputCount = 0 |
194 |
this.tagPattern = Cookie.get('tagPattern') || ".*" |
195 |
|
196 |
// I hate writing javascript in HTML... but what's a better alternative |
197 |
this.logElement = document.createElement('div') |
198 |
document.body.appendChild(this.logElement) |
199 |
Element.hide(this.logElement) |
200 |
|
201 |
this.logElement.style.position = "absolute" |
202 |
this.logElement.style.left = '0px' |
203 |
this.logElement.style.width = '100%' |
204 |
|
205 |
this.logElement.style.textAlign = "left" |
206 |
this.logElement.style.fontFamily = "lucida console" |
207 |
this.logElement.style.fontSize = "100%" |
208 |
this.logElement.style.backgroundColor = 'darkgray' |
209 |
this.logElement.style.opacity = 0.9 |
210 |
this.logElement.style.zIndex = 2000 |
211 |
|
212 |
// Add toolbarElement |
213 |
this.toolbarElement = document.createElement('div') |
214 |
this.logElement.appendChild(this.toolbarElement) |
215 |
this.toolbarElement.style.padding = "0 0 0 2px" |
216 |
|
217 |
// Add buttons |
218 |
this.buttonsContainerElement = document.createElement('span') |
219 |
this.toolbarElement.appendChild(this.buttonsContainerElement) |
220 |
|
221 |
this.buttonsContainerElement.innerHTML += '<button onclick="logConsole.toggle()" style="float:right;color:black">close</button>' |
222 |
this.buttonsContainerElement.innerHTML += '<button onclick="Logger.clear()" style="float:right;color:black">clear</button>' |
223 |
|
224 |
|
225 |
//Add Tag Filter |
226 |
this.tagFilterContainerElement = document.createElement('span') |
227 |
this.toolbarElement.appendChild(this.tagFilterContainerElement) |
228 |
this.tagFilterContainerElement.style.cssFloat = 'left' |
229 |
this.tagFilterContainerElement.appendChild(document.createTextNode("Log Filter")) |
230 |
|
231 |
this.tagFilterElement = document.createElement('input') |
232 |
this.tagFilterContainerElement.appendChild(this.tagFilterElement) |
233 |
this.tagFilterElement.style.width = '200px' |
234 |
this.tagFilterElement.value = this.tagPattern |
235 |
this.tagFilterElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out |
236 |
|
237 |
Event.observe(this.tagFilterElement, 'keyup', this.updateTags.bind(this)) |
238 |
Event.observe(this.tagFilterElement, 'click', function() {this.tagFilterElement.select()}.bind(this)) |
239 |
|
240 |
// Add outputElement |
241 |
this.outputElement = document.createElement('div') |
242 |
this.logElement.appendChild(this.outputElement) |
243 |
this.outputElement.style.overflow = "auto" |
244 |
this.outputElement.style.clear = "both" |
245 |
this.outputElement.style.height = "200px" |
246 |
this.outputElement.style.backgroundColor = 'black' |
247 |
|
248 |
this.inputContainerElement = document.createElement('div') |
249 |
this.inputContainerElement.style.width = "100%" |
250 |
this.logElement.appendChild(this.inputContainerElement) |
251 |
|
252 |
this.inputElement = document.createElement('input') |
253 |
this.inputContainerElement.appendChild(this.inputElement) |
254 |
this.inputElement.style.width = '100%' |
255 |
this.inputElement.style.borderWidth = '0px' // Inputs with 100% width always seem to be too large (I HATE THEM) they only work if the border, margin and padding are 0 |
256 |
this.inputElement.style.margin = '0px' |
257 |
this.inputElement.style.padding = '0px' |
258 |
this.inputElement.value = 'Type command here' |
259 |
this.inputElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out |
260 |
|
261 |
Event.observe(this.inputElement, 'keyup', this.handleInput.bind(this)) |
262 |
Event.observe(this.inputElement, 'click', function() {this.inputElement.select()}.bind(this)) |
263 |
|
264 |
window.setInterval(this.repositionWindow.bind(this), 500) |
265 |
this.repositionWindow() |
266 |
|
267 |
// Listen to the logger.... |
268 |
Logger.onupdate.addListener(this.logUpdate.bind(this)) |
269 |
Logger.onclear.addListener(this.clear.bind(this)) |
270 |
|
271 |
// Preload log element with the log entries that have been entered |
272 |
for (var i = 0; i < Logger.logEntries.length; i++) { |
273 |
this.logUpdate(Logger.logEntries[i]) |
274 |
} |
275 |
|
276 |
// Feed all errors into the logger (For some unknown reason I can only get this to work |
277 |
// with an inline event declaration) |
278 |
Event.observe(window, 'error', function(msg, url, lineNumber) {Logger.error("Error in (" + (url || location) + ") on line "+lineNumber+"", msg)}) |
279 |
|
280 |
// Allow acess key link |
281 |
var accessElement = document.createElement('span') |
282 |
accessElement.innerHTML = '<button style="position:absolute;top:-100px" onclick="javascript:logConsole.toggle()" accesskey="d"></button>' |
283 |
document.body.appendChild(accessElement) |
284 |
|
285 |
if (Cookie.get('ConsoleVisible') == 'true') { |
286 |
this.toggle() |
287 |
} |
288 |
}, |
289 |
|
290 |
toggle : function() { |
291 |
if (this.logElement.style.display == 'none') { |
292 |
this.show() |
293 |
} |
294 |
else { |
295 |
this.hide() |
296 |
} |
297 |
}, |
298 |
|
299 |
show : function() { |
300 |
Element.show(this.logElement) |
301 |
this.outputElement.scrollTop = this.outputElement.scrollHeight // Scroll to bottom when toggled |
302 |
Cookie.set('ConsoleVisible', 'true') |
303 |
this.inputElement.select() |
304 |
}, |
305 |
|
306 |
hide : function() { |
307 |
Element.hide(this.logElement) |
308 |
Cookie.set('ConsoleVisible', 'false') |
309 |
}, |
310 |
|
311 |
output : function(message, style) { |
312 |
// If we are at the bottom of the window, then keep scrolling with the output |
313 |
var shouldScroll = (this.outputElement.scrollTop + (2 * this.outputElement.clientHeight)) >= this.outputElement.scrollHeight |
314 |
|
315 |
this.outputCount++ |
316 |
style = (style ? style += ';' : '') |
317 |
style += 'padding:1px;margin:0 0 5px 0' |
318 |
|
319 |
if (this.outputCount % 2 == 0) style += ";background-color:#101010" |
320 |
|
321 |
message = message || "undefined" |
322 |
message = message.toString().escapeHTML() |
323 |
|
324 |
this.outputElement.innerHTML += "<pre style='" + style + "'>" + message + "</pre>" |
325 |
|
326 |
if (shouldScroll) { |
327 |
this.outputElement.scrollTop = this.outputElement.scrollHeight |
328 |
} |
329 |
}, |
330 |
|
331 |
updateTags : function() { |
332 |
var pattern = this.tagFilterElement.value |
333 |
|
334 |
if (this.tagPattern == pattern) return |
335 |
|
336 |
try { |
337 |
new RegExp(pattern) |
338 |
} |
339 |
catch (e) { |
340 |
return |
341 |
} |
342 |
|
343 |
this.tagPattern = pattern |
344 |
Cookie.set('tagPattern', this.tagPattern) |
345 |
|
346 |
this.outputElement.innerHTML = "" |
347 |
|
348 |
// Go through each log entry again |
349 |
this.outputCount = 0; |
350 |
for (var i = 0; i < Logger.logEntries.length; i++) { |
351 |
this.logUpdate(Logger.logEntries[i]) |
352 |
} |
353 |
}, |
354 |
|
355 |
repositionWindow : function() { |
356 |
var offset = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop |
357 |
var pageHeight = self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight |
358 |
this.logElement.style.top = (offset + pageHeight - Element.getHeight(this.logElement)) + "px" |
359 |
}, |
360 |
|
361 |
// Event Handlers |
362 |
// -------------- |
363 |
|
364 |
logUpdate : function(logEntry) { |
365 |
if (logEntry.tag.search(new RegExp(this.tagPattern, 'igm')) == -1) return |
366 |
var style = '' |
367 |
if (logEntry.tag.search(/error/) != -1) style += 'color:red' |
368 |
else if (logEntry.tag.search(/warning/) != -1) style += 'color:orange' |
369 |
else if (logEntry.tag.search(/debug/) != -1) style += 'color:green' |
370 |
else if (logEntry.tag.search(/info/) != -1) style += 'color:white' |
371 |
else style += 'color:yellow' |
372 |
|
373 |
this.output(logEntry.message, style) |
374 |
}, |
375 |
|
376 |
clear : function(e) { |
377 |
this.outputElement.innerHTML = "" |
378 |
}, |
379 |
|
380 |
handleInput : function(e) { |
381 |
if (e.keyCode == Event.KEY_RETURN ) { |
382 |
var command = this.inputElement.value |
383 |
|
384 |
switch(command) { |
385 |
case "clear": |
386 |
Logger.clear() |
387 |
break |
388 |
|
389 |
default: |
390 |
var consoleOutput = "" |
391 |
|
392 |
try { |
393 |
consoleOutput = eval(this.inputElement.value) |
394 |
} |
395 |
catch (e) { |
396 |
Logger.error("Problem parsing input <" + command + ">", e) |
397 |
break |
398 |
} |
399 |
|
400 |
Logger.log(consoleOutput) |
401 |
break |
402 |
} |
403 |
|
404 |
if (this.inputElement.value != "" && this.inputElement.value != this.commandHistory[0]) { |
405 |
this.commandHistory.unshift(this.inputElement.value) |
406 |
} |
407 |
|
408 |
this.commandIndex = 0 |
409 |
this.inputElement.value = "" |
410 |
} |
411 |
else if (e.keyCode == Event.KEY_UP && this.commandHistory.length > 0) { |
412 |
this.inputElement.value = this.commandHistory[this.commandIndex] |
413 |
|
414 |
if (this.commandIndex < this.commandHistory.length - 1) { |
415 |
this.commandIndex += 1 |
416 |
} |
417 |
} |
418 |
else if (e.keyCode == Event.KEY_DOWN && this.commandHistory.length > 0) { |
419 |
if (this.commandIndex > 0) { |
420 |
this.commandIndex -= 1 |
421 |
} |
422 |
|
423 |
this.inputElement.value = this.commandHistory[this.commandIndex] |
424 |
} |
425 |
else { |
426 |
this.commandIndex = 0 |
427 |
} |
428 |
} |
429 |
} |
430 |
|
431 |
// Load the Console when the window loads |
432 |
var logConsole = null; |
433 |
Event.observe(window, "load", function() {logConsole = new LogConsole()}) |
434 |
|
435 |
// ------------------------- |
436 |
// Helper Functions And Junk |
437 |
// ------------------------- |
438 |
function inspect(element, hideProperties, hideMethods) { |
439 |
var properties = [] |
440 |
var methods = [] |
441 |
|
442 |
element = $(element) |
443 |
|
444 |
for(var internal in element) { |
445 |
try { |
446 |
if (element[internal] instanceof Function) { |
447 |
if (!hideMethods) methods.push(internal + ":\t" + element[internal] ) |
448 |
} |
449 |
else { |
450 |
if (!hideProperties) properties.push(internal + ":\t" + element[internal] ) |
451 |
} |
452 |
} |
453 |
catch (e) { |
454 |
Logger.error("Excetion thrown while inspecting object.", e) |
455 |
} |
456 |
} |
457 |
|
458 |
properties.sort() |
459 |
methods.sort() |
460 |
|
461 |
var internals = properties.concat(methods) |
462 |
var output = "" |
463 |
for (var i = 0; i < internals.length; i++) { |
464 |
output += (internals[i] + "\n") |
465 |
} |
466 |
|
467 |
return output |
468 |
} |
469 |
|
470 |
Array.prototype.contains = function(object) { |
471 |
for(var i = 0; i < this.length; i++) { |
472 |
if (object == this[i]) return true |
473 |
} |
474 |
|
475 |
return false |
476 |
} |
477 |
|
478 |
// Helper Alias for simple logging |
479 |
var puts = function() {return Logger.log(arguments[0], arguments[1])} |