3 Created By: Corey Johnson
4 E-mail: probablyCorey@gmail.com
6 Requires: Prototype Javascript library (http://prototype.conio.net/)
8 Use it all you want. Just remember to give me some credit :)
16 CustomEvent = jQuery.klass({
17 initialize : function() {
21 addListener : function(method) {
22 this.listeners.push(method)
25 removeListener : function(method) {
26 var foundIndexes = this._findListenerIndexes(method)
28 for(var i = 0; i < foundIndexes.length; i++) {
29 this.listeners.splice(foundIndexes[i], 1)
33 dispatch : function(handler) {
34 for(var i = 0; i < this.listeners.length; i++) {
36 this.listeners[i](handler)
39 alert("Could not run the listener " + this.listeners[i] + ". " + e)
46 _findListenerIndexes : function(method) {
48 for(var i = 0; i < this.listeners.length; i++) {
49 if (this.listeners[i] == method) {
63 set : function(name, value, expirationInDays, path) {
64 var cookie = escape(name) + "=" + escape(value)
66 if (expirationInDays) {
68 date.setDate(date.getDate() + expirationInDays)
69 cookie += "; expires=" + date.toGMTString()
73 cookie += ";path=" + path
76 document.cookie = cookie
78 if (value && (expirationInDays == undefined || expirationInDays > 0) && !this.get(name)) {
79 Logger.error("Cookie (" + name + ") was not set correctly... The value was " + value.toString().length + " charachters long (This may be over the cookie limit)");
83 get : function(name) {
84 var pattern = "(^|;)\\s*" + escape(name) + "=([^;]+)"
86 var m = document.cookie.match(pattern)
94 var cookies = document.cookie.split(';')
97 for (var i = 0; i < cookies.length; i++) {
99 var name = unescape(cookies[i].match(/^\s*([^=]+)/m)[1])
100 var value = unescape(cookies[i].match(/=(.*$)/m)[1])
106 cookieArray.push({name : name, value : value})
108 if (cookieArray[name] != undefined) {
109 Logger.waring("Trying to retrieve cookie named(" + name + "). There appears to be another property with this name though.");
112 cookieArray[name] = value
118 clear : function(name) {
119 this.set(name, "", -1)
122 clearAll : function() {
123 var cookies = this.getAll()
125 for(var i = 0; i < cookies.length; i++) {
126 this.clear(cookies[i].name)
139 onupdate : new CustomEvent(),
140 onclear : new CustomEvent(),
144 log : function(message, tag) {
145 var logEntry = new LogEntry(message, tag || "info")
146 this.logEntries.push(logEntry)
147 this.onupdate.dispatch(logEntry)
150 info : function(message) {
151 this.log(message, 'info')
152 if(typeof(console) != "undefined")
153 console.info(message);
156 debug : function(message) {
157 this.log(message, 'debug')
158 if(typeof(console) != "undefined")
159 console.debug(message);
162 warn : function(message) {
163 this.log(message, 'warning')
164 if(typeof(console) != "undefined")
165 console.warn(message);
168 error : function(message, error) {
169 this.log(message + ": \n" + error, 'error')
170 if(typeof(console) != "undefined")
171 console.error(message + ": \n" + error);
175 clear : function () {
177 this.onclear.dispatch()
181 LogEntry = jQuery.klass({
182 initialize : function(message, tag) {
183 this.message = message
188 LogConsole = jQuery.klass({
200 initialize : function(toggleKey) {
202 this.tagPattern = Cookie.get('tagPattern') || ".*"
204 // I hate writing javascript in HTML... but what's a better alternative
205 this.logElement = document.createElement('div')
206 document.body.appendChild(this.logElement)
207 jQuery(this.logElement).hide();
209 this.logElement.style.position = "absolute"
210 this.logElement.style.left = '0px'
211 this.logElement.style.width = '100%'
213 this.logElement.style.textAlign = "left"
214 this.logElement.style.fontFamily = "lucida console"
215 this.logElement.style.fontSize = "100%"
216 this.logElement.style.backgroundColor = 'darkgray'
217 this.logElement.style.opacity = 0.9
218 this.logElement.style.zIndex = 2000
220 // Add toolbarElement
221 this.toolbarElement = document.createElement('div')
222 this.logElement.appendChild(this.toolbarElement)
223 this.toolbarElement.style.padding = "0 0 0 2px"
226 this.buttonsContainerElement = document.createElement('span')
227 this.toolbarElement.appendChild(this.buttonsContainerElement)
229 this.buttonsContainerElement.innerHTML += '<button onclick="logConsole.toggle()" style="float:right;color:black">close</button>'
230 this.buttonsContainerElement.innerHTML += '<button onclick="Logger.clear()" style="float:right;color:black">clear</button>'
231 if(!Prado.Inspector.disabled)
232 this.buttonsContainerElement.innerHTML += '<button onclick="Prado.Inspector.inspect()" style="float:right;color:black; margin-right:15px;">Object Tree</button>'
236 this.tagFilterContainerElement = document.createElement('span')
237 this.toolbarElement.appendChild(this.tagFilterContainerElement)
238 this.tagFilterContainerElement.style.cssFloat = 'left'
239 this.tagFilterContainerElement.appendChild(document.createTextNode("Log Filter"))
241 this.tagFilterElement = document.createElement('input')
242 this.tagFilterContainerElement.appendChild(this.tagFilterElement)
243 this.tagFilterElement.style.width = '200px'
244 this.tagFilterElement.value = this.tagPattern
245 this.tagFilterElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out
247 jQuery(this.tagFilterElement).on('keyup', this.updateTags.bind(this));
248 jQuery(this.tagFilterElement).on('click', function() {this.tagFilterElement.select()}.bind(this));
251 this.outputElement = document.createElement('div')
252 this.logElement.appendChild(this.outputElement)
253 this.outputElement.style.overflow = "auto"
254 this.outputElement.style.clear = "both"
255 this.outputElement.style.height = "200px"
256 this.outputElement.style.backgroundColor = 'black'
258 this.inputContainerElement = document.createElement('div')
259 this.inputContainerElement.style.width = "100%"
260 this.logElement.appendChild(this.inputContainerElement)
262 this.inputElement = document.createElement('input')
263 this.inputContainerElement.appendChild(this.inputElement)
264 this.inputElement.style.width = '100%'
265 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
266 this.inputElement.style.margin = '0px'
267 this.inputElement.style.padding = '0px'
268 this.inputElement.value = 'Type command here'
269 this.inputElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out
271 jQuery(this.inputElement).on('keyup', this.handleInput.bind(this));
272 jQuery(this.inputElement).on('click', function() {this.inputElement.select()}.bind(this));
274 if(document.all && !window.opera)
276 window.setInterval(this.repositionWindow.bind(this), 500)
280 this.logElement.style.position="fixed";
281 this.logElement.style.bottom="0px";
284 jQuery(document).on('keydown', function(e)
286 if((e.altKey==true) && e.keyCode == toggleKey ) //Alt+J | Ctrl+J
290 // Listen to the logger....
291 Logger.onupdate.addListener(this.logUpdate.bind(this))
292 Logger.onclear.addListener(this.clear.bind(this))
294 // Preload log element with the log entries that have been entered
295 for (var i = 0; i < Logger.logEntries.length; i++) {
296 this.logUpdate(Logger.logEntries[i])
299 // Feed all errors into the logger (For some unknown reason I can only get this to work
300 // with an inline event declaration)
301 jQuery(window).on('error', function(msg, url, lineNumber) {Logger.error("Error in (" + (url || location) + ") on line "+lineNumber+"", msg)});
303 // Allow acess key link
304 var accessElement = document.createElement('span')
305 accessElement.innerHTML = '<button style="position:absolute;top:-100px" onclick="javascript:logConsole.toggle()" accesskey="d"></button>'
306 document.body.appendChild(accessElement)
308 if (Cookie.get('ConsoleVisible') == 'true') {
313 toggle : function() {
314 if (this.logElement.style.display == 'none') {
323 jQuery(this.logElement).show();
324 this.outputElement.scrollTop = this.outputElement.scrollHeight // Scroll to bottom when toggled
325 if(document.all && !window.opera)
326 this.repositionWindow();
327 Cookie.set('ConsoleVisible', 'true')
328 this.inputElement.select();
334 jQuery(this.logElement).hide();
335 Cookie.set('ConsoleVisible', 'false');
338 output : function(message, style) {
339 // If we are at the bottom of the window, then keep scrolling with the output
340 var shouldScroll = (this.outputElement.scrollTop + (2 * this.outputElement.clientHeight)) >= this.outputElement.scrollHeight
343 style = (style ? style += ';' : '')
344 style += 'padding:1px;margin:0 0 5px 0'
346 if (this.outputCount % 2 == 0) style += ";background-color:#101010"
348 message = message || "undefined"
349 message = message.toString().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
351 this.outputElement.innerHTML += "<pre style='" + style + "'>" + message + "</pre>"
354 this.outputElement.scrollTop = this.outputElement.scrollHeight
358 updateTags : function() {
359 var pattern = this.tagFilterElement.value
361 if (this.tagPattern == pattern) return
370 this.tagPattern = pattern
371 Cookie.set('tagPattern', this.tagPattern)
373 this.outputElement.innerHTML = ""
375 // Go through each log entry again
376 this.outputCount = 0;
377 for (var i = 0; i < Logger.logEntries.length; i++) {
378 this.logUpdate(Logger.logEntries[i])
382 repositionWindow : function() {
383 var offset = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
384 var pageHeight = self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
385 this.logElement.style.top = (offset + pageHeight - Element.getHeight(this.logElement)) + "px"
391 logUpdate : function(logEntry) {
392 if (logEntry.tag.search(new RegExp(this.tagPattern, 'igm')) == -1) return
394 if (logEntry.tag.search(/error/) != -1) style += 'color:red'
395 else if (logEntry.tag.search(/warning/) != -1) style += 'color:orange'
396 else if (logEntry.tag.search(/debug/) != -1) style += 'color:green'
397 else if (logEntry.tag.search(/info/) != -1) style += 'color:white'
398 else style += 'color:yellow'
400 this.output(logEntry.message, style)
403 clear : function(e) {
404 this.outputElement.innerHTML = ""
407 handleInput : function(e) {
408 if (e.keyCode == 13 ) {
409 var command = this.inputElement.value
417 var consoleOutput = ""
420 consoleOutput = eval(this.inputElement.value)
423 Logger.error("Problem parsing input <" + command + ">", e)
427 Logger.log(consoleOutput)
431 if (this.inputElement.value != "" && this.inputElement.value != this.commandHistory[0]) {
432 this.commandHistory.unshift(this.inputElement.value)
435 this.commandIndex = 0
436 this.inputElement.value = ""
438 else if (e.keyCode == 38 && this.commandHistory.length > 0) {
439 this.inputElement.value = this.commandHistory[this.commandIndex]
441 if (this.commandIndex < this.commandHistory.length - 1) {
442 this.commandIndex += 1
445 else if (e.keyCode == 40 && this.commandHistory.length > 0) {
446 if (this.commandIndex > 0) {
447 this.commandIndex -= 1
450 this.inputElement.value = this.commandHistory[this.commandIndex]
453 this.commandIndex = 0
459 // -------------------------
460 // Helper Functions And Junk
461 // -------------------------
464 var objtype = typeof(o);
465 if (objtype == "undefined") {
467 } else if (objtype == "number" || objtype == "boolean") {
469 } else if (o === null) {
474 var ostring = (o + "");
476 return "[" + typeof(o) + "]";
479 if (typeof(o) == "function")
481 o = ostring.replace(/^\s+/, "");
482 var idx = o.indexOf("{");
484 o = o.substr(0, idx) + "{...}";
489 var reprString = function (o)
491 return ('"' + o.replace(/(["\\])/g, '\\$1') + '"'
492 ).replace(/[\f]/g, "\\f"
493 ).replace(/[\b]/g, "\\b"
494 ).replace(/[\n]/g, "\\n"
495 ).replace(/[\t]/g, "\\t"
496 ).replace(/[\r]/g, "\\r");
499 if (objtype == "string") {
500 return reprString(o);
503 var me = arguments.callee;
504 // short-circuit for objects that support "json" serialization
505 // if they return "self" then just pass-through...
507 if (typeof(o.__json__) == "function") {
508 newObj = o.__json__();
513 if (typeof(o.json) == "function") {
520 if (objtype != "function" && typeof(o.length) == "number") {
522 for (var i = 0; i < o.length; i++) {
524 if (typeof(val) != "string") {
529 return "[" + res.join(", ") + "]";
532 // generic object code path
536 if (typeof(k) == "number") {
537 useKey = '"' + k + '"';
538 } else if (typeof(k) == "string") {
539 useKey = reprString(k);
541 // skip non-string or number keys
545 if (typeof(val) != "string") {
546 // skip non-serializable values
549 res.push(useKey + ":" + val);
551 return "{" + res.join(", ") + "}";
554 Array.prototype.contains = function(object) {
555 for(var i = 0; i < this.length; i++) {
556 if (object == this[i]) return true
562 // Helper Alias for simple logging
563 var puts = function() {return Logger.log(arguments[0], arguments[1])};
565 /*************************************
567 Javascript Object Tree
569 last revision:04.11.2004
570 steve@slayeroffice.com
571 http://slayeroffice.com
575 Please notify me of any modifications
576 you make to this code so that I can
577 update the version hosted on slayeroffice.com
580 ************************************/
581 if(typeof Prado == "undefined")
588 hidden : new Array(),
589 opera : window.opera,
591 nameList : new Array(),
593 format : function(str) {
594 if(typeof(str) != "string") return str;
595 str=str.replace(/</g,"<");
596 str=str.replace(/>/g,">");
600 parseJS : function(obj) {
602 if(typeof obj == "string") { name = obj; obj = eval(obj); }
603 win= typeof obj == 'undefined' ? window : obj;
604 this.displaying = name ? name : win.toString();
607 if(win[js] && js.toString().indexOf("Inspector")==-1 && (win[js]+"").indexOf("[native code]")==-1) {
610 if(!this.objs[t.toString()]) {
611 this.types[this.types.length]=t;
613 this.nameList[t] = new Array();
615 this.nameList[t].push(js);
616 this.objs[t][js] = this.format(win[js]+"");
621 for(i=0;i<this.types.length;i++)
622 this.nameList[this.types[i]].sort();
625 show : function(objID) {
626 this.d.getElementById(objID).style.display=this.hidden[objID]?"none":"block";
627 this.hidden[objID]=this.hidden[objID]?0:1;
630 changeSpan : function(spanID) {
631 if(this.d.getElementById(spanID).innerHTML.indexOf("+")>-1){
632 this.d.getElementById(spanID).innerHTML="[-]";
634 this.d.getElementById(spanID).innerHTML="[+]";
638 buildInspectionLevel : function()
640 var display = this.displaying;
641 var list = display.split(".");
642 var links = ["<a href=\"javascript:var_dump()\">[object Window]</a>"];
644 if(display.indexOf("[object ") >= 0) return links.join(".");
645 for(var i = 0; i < list.length; i++)
647 name += (name.length ? "." : "") + list[i];
648 links[i+1] = "<a href=\"javascript:var_dump('"+name+"')\">"+list[i]+"</a>";
650 return links.join(".");
653 buildTree : function() {
654 mHTML = "<div>Inspecting "+this.buildInspectionLevel()+"</div>";
655 mHTML +="<ul class=\"topLevel\">";
658 for(i=0;i<this.types.length;i++)
660 mHTML+="<li style=\"cursor:pointer;\" onclick=\"Prado.Inspector.show('ul"+i+"');Prado.Inspector.changeSpan('sp" + i + "')\"><span id=\"sp" + i + "\">[+]</span><b>" + this.types[i] + "</b> (" + this.nameList[this.types[i]].length + ")</li><ul style=\"display:none;\" id=\"ul"+i+"\">";
661 this.hidden["ul"+i]=0;
662 for(e=0;e<this.nameList[this.types[i]].length;e++)
664 var prop = this.nameList[this.types[i]][e];
665 var value = this.objs[this.types[i]][prop]
667 if(value.indexOf("[object ") >= 0 && /^[a-zA-Z_]/.test(prop))
669 if(this.displaying.indexOf("[object ") < 0)
670 more = " <a href=\"javascript:var_dump('"+this.displaying+"."+prop+"')\"><b>more</b></a>";
671 else if(this.displaying.indexOf("[object Window]") >= 0)
672 more = " <a href=\"javascript:var_dump('"+prop+"')\"><b>more</b></a>";
674 mHTML+="<li style=\"cursor:pointer;\" onclick=\"Prado.Inspector.show('mul" + so_objIndex + "');Prado.Inspector.changeSpan('sk" + so_objIndex + "')\"><span id=\"sk" + so_objIndex + "\">[+]</span>" + prop + "</li><ul id=\"mul" + so_objIndex + "\" style=\"display:none;\"><li style=\"list-style-type:none;\"><pre>" + value + more + "</pre></li></ul>";
675 this.hidden["mul"+so_objIndex]=0;
681 this.d.getElementById("so_mContainer").innerHTML =mHTML;
684 handleKeyEvent : function(e) {
685 keyCode=document.all?window.event.keyCode:e.keyCode;
693 if(this.d.getElementById("so_mContainer"))
695 this.d.body.removeChild(this.d.getElementById("so_mContainer"));
696 this.d.body.removeChild(this.d.getElementById("so_mStyle"));
697 jQuery(this.d).unbind("keydown", this.dKeyDownEvent);
698 this.types = new Array();
699 this.objs = new Array();
700 this.hidden = new Array();
704 disabled : document.all && !this.opera,
706 inspect : function(obj)
708 if(this.disabled)return alert("Sorry, this only works in Mozilla and Firefox currently.");
710 mObj=this.d.body.appendChild(this.d.createElement("div"));
711 mObj.id="so_mContainer";
712 sObj=this.d.body.appendChild(this.d.createElement("style"));
714 sObj.type="text/css";
715 sObj.innerHTML = this.style;
716 this.dKeyDownEvent = this.handleKeyEvent.bind(this);
717 jQuery(this.d).on("keydown", this.dKeyDownEvent);
722 cObj=mObj.appendChild(this.d.createElement("div"));
723 cObj.className="credits";
724 cObj.innerHTML = "<b>[esc] to <a href=\"javascript:Prado.Inspector.cleanUp();\">close</a></b><br />Javascript Object Tree V2.0.";
726 window.scrollTo(0,0);
729 style : "#so_mContainer { position:absolute; top:5px; left:5px; background-color:#E3EBED; text-align:left; font:9pt verdana; width:85%; border:2px solid #000; padding:5px; z-index:1000; color:#000; } " +
730 "#so_mContainer ul { padding-left:20px; } " +
731 "#so_mContainer ul li { display:block; list-style-type:none; list-style-image:url(); line-height:2em; -moz-border-radius:.75em; font:10px verdana; padding:0; margin:2px; color:#000; } " +
732 "#so_mContainer li:hover { background-color:#E3EBED; } " +
733 "#so_mContainer ul li span { position:relative; width:15px; height:15px; margin-right:4px; } " +
734 "#so_mContainer pre { background-color:#F9FAFB; border:1px solid #638DA1; height:auto; padding:5px; font:9px verdana; color:#000; } " +
735 "#so_mContainer .topLevel { margin:0; padding:0; } " +
736 "#so_mContainer .credits { float:left; width:200px; font:6.5pt verdana; color:#000; padding:2px; margin-left:5px; text-align:left; border-top:1px solid #000; margin-top:15px; width:75%; } " +
737 "#so_mContainer .credits a { font:9px verdana; font-weight:bold; color:#004465; text-decoration:none; background-color:transparent; }"
740 //similar function to var_dump in PHP, brings up the javascript object tree UI.
741 function var_dump(obj)
743 Prado.Inspector.inspect(obj);
746 //similar function to print_r for PHP
747 var print_r = inspect;