]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/framework/Web/Javascripts/source/prado/logger/logger.js
fde772ce4550595461c92da59b7ce0575eb6c3e5
[bacula/bacula] / gui / baculum / framework / Web / Javascripts / source / prado / logger / logger.js
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 CustomEvent = jQuery.klass({
17   initialize : function() {
18         this.listeners = []
19   },
20
21         addListener : function(method) {
22                 this.listeners.push(method)
23         },
24
25         removeListener : function(method) {
26                 var foundIndexes = this._findListenerIndexes(method)
27
28                 for(var i = 0; i < foundIndexes.length; i++) {
29                         this.listeners.splice(foundIndexes[i], 1)
30                 }
31         },
32
33         dispatch : function(handler) {
34                 for(var i = 0; i < this.listeners.length; i++) {
35                         try {
36                                 this.listeners[i](handler)
37                         }
38                         catch (e) {
39                                 alert("Could not run the listener " + this.listeners[i] + ". " + e)
40                         }
41                 }
42         },
43
44         // Private Methods
45         // ---------------
46         _findListenerIndexes : function(method) {
47                 var indexes = []
48                 for(var i = 0; i < this.listeners.length; i++) {
49                         if (this.listeners[i] == method) {
50                                 indexes.push(i)
51                         }
52                 }
53
54                 return indexes
55         }
56 });
57
58 // ------
59 // Cookie
60 // ------
61
62 var Cookie = {
63         set : function(name, value, expirationInDays, path) {
64                 var cookie = escape(name) + "=" + escape(value)
65
66                 if (expirationInDays) {
67                         var date = new Date()
68                         date.setDate(date.getDate() + expirationInDays)
69                         cookie += "; expires=" + date.toGMTString()
70                 }
71
72                 if (path) {
73                         cookie += ";path=" + path
74                 }
75
76                 document.cookie = cookie
77
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)");
80                 }
81         },
82
83         get : function(name) {
84                 var pattern = "(^|;)\\s*" + escape(name) + "=([^;]+)"
85
86                 var m = document.cookie.match(pattern)
87                 if (m && m[2]) {
88                         return unescape(m[2])
89                 }
90                 else return null
91         },
92
93         getAll : function() {
94                 var cookies = document.cookie.split(';')
95                 var cookieArray = []
96
97                 for (var i = 0; i < cookies.length; i++) {
98                         try {
99                                 var name = unescape(cookies[i].match(/^\s*([^=]+)/m)[1])
100                                 var value = unescape(cookies[i].match(/=(.*$)/m)[1])
101                         }
102                         catch (e) {
103                                 continue
104                         }
105
106                         cookieArray.push({name : name, value : value})
107
108                         if (cookieArray[name] != undefined) {
109                                 Logger.waring("Trying to retrieve cookie named(" + name + "). There appears to be another property with this name though.");
110                         }
111
112                         cookieArray[name] = value
113                 }
114
115                 return cookieArray
116         },
117
118         clear : function(name) {
119                 this.set(name, "", -1)
120         },
121
122         clearAll : function() {
123                 var cookies = this.getAll()
124
125                 for(var i = 0; i < cookies.length; i++) {
126                         this.clear(cookies[i].name)
127                 }
128
129         }
130 };
131
132 // ------
133 // Logger
134 // -----
135
136 Logger = {
137         logEntries : [],
138
139         onupdate : new CustomEvent(),
140         onclear : new CustomEvent(),
141
142
143         // Logger output
144   log : function(message, tag) {
145           var logEntry = new LogEntry(message, tag || "info")
146                 this.logEntries.push(logEntry)
147                 this.onupdate.dispatch(logEntry)
148         },
149
150         info : function(message) {
151                 this.log(message, 'info')
152                 if(typeof(console) != "undefined")
153                         console.info(message);
154         },
155
156         debug : function(message) {
157                 this.log(message, 'debug')
158                 if(typeof(console) != "undefined")
159                         console.debug(message);
160         },
161
162         warn : function(message) {
163           this.log(message, 'warning')
164                 if(typeof(console) != "undefined")
165                         console.warn(message);
166         },
167
168         error : function(message, error) {
169           this.log(message + ": \n" + error, 'error')
170                 if(typeof(console) != "undefined")
171                         console.error(message + ": \n" + error);
172
173         },
174
175         clear : function () {
176                 this.logEntries = []
177                 this.onclear.dispatch()
178         }
179 };
180
181 LogEntry = jQuery.klass({
182     initialize : function(message, tag) {
183       this.message = message
184       this.tag = tag
185     }
186 });
187
188 LogConsole = jQuery.klass({
189
190   // Properties
191   // ----------
192   commandHistory : [],
193   commandIndex : 0,
194
195   hidden : true,
196
197   // Methods
198   // -------
199
200   initialize : function(toggleKey) {
201     this.outputCount = 0
202     this.tagPattern = Cookie.get('tagPattern') || ".*"
203
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();
208
209         this.logElement.style.position = "absolute"
210     this.logElement.style.left = '0px'
211     this.logElement.style.width = '100%'
212
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
219
220     // Add toolbarElement
221     this.toolbarElement = document.createElement('div')
222     this.logElement.appendChild(this.toolbarElement)
223     this.toolbarElement.style.padding = "0 0 0 2px"
224
225     // Add buttons
226     this.buttonsContainerElement = document.createElement('span')
227     this.toolbarElement.appendChild(this.buttonsContainerElement)
228
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>'
233
234
235                 //Add Tag Filter
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"))
240
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
246
247     jQuery(this.tagFilterElement).on('keyup', this.updateTags.bind(this));
248     jQuery(this.tagFilterElement).on('click', function() {this.tagFilterElement.select()}.bind(this));
249
250     // Add outputElement
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'
257
258     this.inputContainerElement = document.createElement('div')
259     this.inputContainerElement.style.width = "100%"
260     this.logElement.appendChild(this.inputContainerElement)
261
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
270
271     jQuery(this.inputElement).on('keyup', this.handleInput.bind(this));
272     jQuery(this.inputElement).on('click', function() {this.inputElement.select()}.bind(this));
273
274         if(document.all && !window.opera)
275         {
276                 window.setInterval(this.repositionWindow.bind(this), 500)
277         }
278         else
279         {
280                 this.logElement.style.position="fixed";
281                 this.logElement.style.bottom="0px";
282         }
283         var self=this;
284         jQuery(document).on('keydown', function(e)
285         {
286                 if((e.altKey==true) && e.keyCode == toggleKey ) //Alt+J | Ctrl+J
287                         self.toggle();
288         });
289
290     // Listen to the logger....
291     Logger.onupdate.addListener(this.logUpdate.bind(this))
292     Logger.onclear.addListener(this.clear.bind(this))
293
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])
297         }
298
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)});
302
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)
307
308         if (Cookie.get('ConsoleVisible') == 'true') {
309                   this.toggle()
310                 }
311         },
312
313         toggle : function() {
314           if (this.logElement.style.display == 'none') {
315                   this.show();
316                 }
317                 else {
318                         this.hide();
319                 }
320         },
321
322         show : function() {
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();
329           this.hidden = false;
330         },
331
332         hide : function() {
333           this.hidden = true;
334           jQuery(this.logElement).hide();
335           Cookie.set('ConsoleVisible', 'false');
336         },
337
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
341
342                         this.outputCount++
343                 style = (style ? style += ';' : '')
344                 style += 'padding:1px;margin:0 0 5px 0'
345
346                   if (this.outputCount % 2 == 0) style += ";background-color:#101010"
347
348                 message = message || "undefined"
349                 message = message.toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
350
351                 this.outputElement.innerHTML += "<pre style='" + style + "'>" + message + "</pre>"
352
353                 if (shouldScroll) {
354                                 this.outputElement.scrollTop = this.outputElement.scrollHeight
355                         }
356         },
357
358         updateTags : function() {
359                 var pattern = this.tagFilterElement.value
360
361                 if (this.tagPattern == pattern) return
362
363                 try {
364                         new RegExp(pattern)
365                 }
366                 catch (e) {
367                         return
368                 }
369
370                 this.tagPattern = pattern
371                 Cookie.set('tagPattern', this.tagPattern)
372
373                 this.outputElement.innerHTML = ""
374
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])
379         }
380         },
381
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"
386         },
387
388         // Event Handlers
389         // --------------
390
391         logUpdate : function(logEntry) {
392                 if (logEntry.tag.search(new RegExp(this.tagPattern, 'igm')) == -1) return
393                 var style = ''
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'
399
400                 this.output(logEntry.message, style)
401         },
402
403         clear : function(e) {
404                 this.outputElement.innerHTML = ""
405         },
406
407         handleInput : function(e) {
408                 if (e.keyCode == 13 ) {
409                 var command = this.inputElement.value
410
411                 switch(command) {
412                 case "clear":
413                         Logger.clear()
414                         break
415
416                 default:
417                 var consoleOutput = ""
418
419                 try {
420                         consoleOutput = eval(this.inputElement.value)
421                 }
422                 catch (e) {
423                         Logger.error("Problem parsing input <" + command + ">", e)
424                         break
425                                         }
426
427                                         Logger.log(consoleOutput)
428                 break
429                         }
430
431                 if (this.inputElement.value != "" && this.inputElement.value != this.commandHistory[0]) {
432                 this.commandHistory.unshift(this.inputElement.value)
433                 }
434
435                 this.commandIndex = 0
436                 this.inputElement.value = ""
437                 }
438     else if (e.keyCode == 38 && this.commandHistory.length > 0) {
439         this.inputElement.value = this.commandHistory[this.commandIndex]
440
441                         if (this.commandIndex < this.commandHistory.length - 1) {
442         this.commandIndex += 1
443       }
444     }
445     else if (e.keyCode == 40 && this.commandHistory.length > 0) {
446         if (this.commandIndex > 0) {
447         this.commandIndex -= 1
448             }
449
450                         this.inputElement.value = this.commandHistory[this.commandIndex]
451           }
452                 else {
453                 this.commandIndex = 0
454     }
455         }
456 });
457
458
459 // -------------------------
460 // Helper Functions And Junk
461 // -------------------------
462 function inspect(o)
463 {
464         var objtype = typeof(o);
465         if (objtype == "undefined") {
466                 return "undefined";
467         } else if (objtype == "number" || objtype == "boolean") {
468                 return o + "";
469         } else if (o === null) {
470                 return "null";
471         }
472
473          try {
474             var ostring = (o + "");
475         } catch (e) {
476             return "[" + typeof(o) + "]";
477         }
478
479         if (typeof(o) == "function")
480         {
481             o = ostring.replace(/^\s+/, "");
482             var idx = o.indexOf("{");
483             if (idx != -1) {
484                 o = o.substr(0, idx) + "{...}";
485             }
486                         return o;
487        }
488
489         var reprString = function (o)
490         {
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");
497         };
498
499         if (objtype == "string") {
500                 return reprString(o);
501         }
502         // recurse
503         var me = arguments.callee;
504         // short-circuit for objects that support "json" serialization
505         // if they return "self" then just pass-through...
506         var newObj;
507         if (typeof(o.__json__) == "function") {
508                 newObj = o.__json__();
509                 if (o !== newObj) {
510                         return me(newObj);
511                 }
512         }
513         if (typeof(o.json) == "function") {
514                 newObj = o.json();
515                 if (o !== newObj) {
516                         return me(newObj);
517                 }
518         }
519         // array
520         if (objtype != "function" && typeof(o.length) == "number") {
521                 var res = [];
522                 for (var i = 0; i < o.length; i++) {
523                         var val = me(o[i]);
524                         if (typeof(val) != "string") {
525                                 val = "undefined";
526                         }
527                         res.push(val);
528                 }
529                 return "[" + res.join(", ") + "]";
530         }
531
532         // generic object code path
533         res = [];
534         for (var k in o) {
535                 var useKey;
536                 if (typeof(k) == "number") {
537                         useKey = '"' + k + '"';
538                 } else if (typeof(k) == "string") {
539                         useKey = reprString(k);
540                 } else {
541                         // skip non-string or number keys
542                         continue;
543                 }
544                 val = me(o[k]);
545                 if (typeof(val) != "string") {
546                         // skip non-serializable values
547                         continue;
548                 }
549                 res.push(useKey + ":" + val);
550         }
551         return "{" + res.join(", ") + "}";
552 };
553
554 Array.prototype.contains = function(object) {
555         for(var i = 0; i < this.length; i++) {
556                 if (object == this[i]) return true
557         }
558
559         return false
560 };
561
562 // Helper Alias for simple logging
563 var puts = function() {return Logger.log(arguments[0], arguments[1])};
564
565 /*************************************
566
567         Javascript Object Tree
568         version 1.0
569         last revision:04.11.2004
570         steve@slayeroffice.com
571         http://slayeroffice.com
572
573         (c)2004 S.G. Chipman
574
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
578
579
580 ************************************/
581 if(typeof Prado == "undefined")
582         var Prado = {};
583 Prado.Inspector =
584 {
585         d : document,
586         types : new Array(),
587         objs : new Array(),
588         hidden : new Array(),
589         opera : window.opera,
590         displaying : '',
591         nameList : new Array(),
592
593         format : function(str) {
594                 if(typeof(str) != "string") return str;
595                 str=str.replace(/</g,"&lt;");
596                 str=str.replace(/>/g,"&gt;");
597                 return str;
598         },
599
600         parseJS : function(obj) {
601                 var name;
602                 if(typeof obj == "string") {  name = obj; obj = eval(obj); }
603                 win= typeof obj == 'undefined' ? window : obj;
604                 this.displaying = name ? name : win.toString();
605                 for(js in win) {
606                         try {
607                                 if(win[js] && js.toString().indexOf("Inspector")==-1 && (win[js]+"").indexOf("[native code]")==-1) {
608
609                                         t=typeof(win[js]);
610                                         if(!this.objs[t.toString()]) {
611                                                 this.types[this.types.length]=t;
612                                                 this.objs[t]={};
613                                                 this.nameList[t] = new Array();
614                                         }
615                                         this.nameList[t].push(js);
616                                         this.objs[t][js] = this.format(win[js]+"");
617                                 }
618                         } catch(err) { }
619                 }
620
621                 for(i=0;i<this.types.length;i++)
622                         this.nameList[this.types[i]].sort();
623         },
624
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;
628         },
629
630         changeSpan : function(spanID) {
631                 if(this.d.getElementById(spanID).innerHTML.indexOf("+")>-1){
632                         this.d.getElementById(spanID).innerHTML="[-]";
633                 } else {
634                         this.d.getElementById(spanID).innerHTML="[+]";
635                 }
636         },
637
638         buildInspectionLevel : function()
639         {
640                 var display = this.displaying;
641                 var list = display.split(".");
642                 var links = ["<a href=\"javascript:var_dump()\">[object Window]</a>"];
643                 var name = '';
644                 if(display.indexOf("[object ") >= 0) return links.join(".");
645                 for(var i = 0; i < list.length; i++)
646                 {
647                         name += (name.length ? "." : "") + list[i];
648                         links[i+1] = "<a href=\"javascript:var_dump('"+name+"')\">"+list[i]+"</a>";
649                 }
650                 return links.join(".");
651         },
652
653         buildTree : function() {
654                 mHTML = "<div>Inspecting "+this.buildInspectionLevel()+"</div>";
655                 mHTML +="<ul class=\"topLevel\">";
656                 this.types.sort();
657                 var so_objIndex=0;
658                 for(i=0;i<this.types.length;i++)
659                 {
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++)
663                         {
664                                 var prop = this.nameList[this.types[i]][e];
665                                 var value = this.objs[this.types[i]][prop]
666                                 var more = "";
667                                 if(value.indexOf("[object ") >= 0 && /^[a-zA-Z_]/.test(prop))
668                                 {
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>";
673                                 }
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;
676                                 so_objIndex++;
677                         }
678                         mHTML+="</ul>";
679                 }
680                 mHTML+="</ul>";
681                 this.d.getElementById("so_mContainer").innerHTML =mHTML;
682         },
683
684         handleKeyEvent : function(e) {
685                 keyCode=document.all?window.event.keyCode:e.keyCode;
686                 if(keyCode==27) {
687                         this.cleanUp();
688                 }
689         },
690
691         cleanUp : function()
692         {
693                 if(this.d.getElementById("so_mContainer"))
694                 {
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();
701                 }
702         },
703
704         disabled : document.all && !this.opera,
705
706         inspect : function(obj)
707         {
708                 if(this.disabled)return alert("Sorry, this only works in Mozilla and Firefox currently.");
709                 this.cleanUp();
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"));
713                 sObj.id="so_mStyle";
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);
718
719                 this.parseJS(obj);
720                 this.buildTree();
721
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.";
725
726                 window.scrollTo(0,0);
727         },
728
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; }"
738 };
739
740 //similar function to var_dump in PHP, brings up the javascript object tree UI.
741 function var_dump(obj)
742 {
743         Prado.Inspector.inspect(obj);
744 }
745
746 //similar function to print_r for PHP
747 var print_r = inspect;
748