rack-goggles 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,730 @@
1
+ /*
2
+ AUTHOR James Padolsey (http://james.padolsey.com)
3
+ VERSION 1.02.1
4
+ UPDATED 07-14-2009
5
+ CONTRIBUTORS
6
+ David Waller
7
+ */
8
+
9
+ var prettyPrint = (function(){
10
+
11
+ /* These "util" functions are not part of the core
12
+ functionality but are all necessary - mostly DOM helpers */
13
+
14
+ var util = {
15
+
16
+ el: function(type, attrs) {
17
+
18
+ /* Create new element */
19
+ var el = document.createElement(type), attr;
20
+
21
+ /*Copy to single object */
22
+ attrs = util.merge({}, attrs);
23
+
24
+ /* Add attributes to el */
25
+ if (attrs && attrs.style) {
26
+ var styles = attrs.style;
27
+ util.applyCSS( el, attrs.style );
28
+ delete attrs.style;
29
+ }
30
+ for (attr in attrs) {
31
+ if (attrs.hasOwnProperty(attr)) {
32
+ el[attr] = attrs[attr];
33
+ }
34
+ }
35
+
36
+ return el;
37
+
38
+ },
39
+
40
+ applyCSS: function(el, styles) {
41
+ /* Applies CSS to a single element */
42
+ for (var prop in styles) {
43
+ if (styles.hasOwnProperty(prop)) {
44
+ try{
45
+ /* Yes, IE6 SUCKS! */
46
+ el.style[prop] = styles[prop];
47
+ }catch(e){}
48
+ }
49
+ }
50
+ },
51
+
52
+ txt: function(t) {
53
+ /* Create text node */
54
+ return document.createTextNode(t);
55
+ },
56
+
57
+ row: function(cells, type, cellType) {
58
+
59
+ /* Creates new <tr> */
60
+ cellType = cellType || 'td';
61
+
62
+ /* colSpan is calculated by length of null items in array */
63
+ var colSpan = util.count(cells, null) + 1,
64
+ tr = util.el('tr'), td,
65
+ attrs = {
66
+ style: util.getStyles(cellType, type),
67
+ colSpan: colSpan,
68
+ onmouseover: function() {
69
+ var tds = this.parentNode.childNodes;
70
+ util.forEach(tds, function(cell){
71
+ if (cell.nodeName.toLowerCase() !== 'td') { return; }
72
+ util.applyCSS(cell, util.getStyles('td_hover', type));
73
+ });
74
+ },
75
+ onmouseout: function() {
76
+ var tds = this.parentNode.childNodes;
77
+ util.forEach(tds, function(cell){
78
+ if (cell.nodeName.toLowerCase() !== 'td') { return; }
79
+ util.applyCSS(cell, util.getStyles('td', type));
80
+ });
81
+ }
82
+ };
83
+
84
+ util.forEach(cells, function(cell){
85
+
86
+ if (cell === null) { return; }
87
+ /* Default cell type is <td> */
88
+ td = util.el(cellType, attrs);
89
+
90
+ if (cell.nodeType) {
91
+ /* IsDomElement */
92
+ td.appendChild(cell);
93
+ } else {
94
+ /* IsString */
95
+ td.innerHTML = util.shorten(cell.toString());
96
+ }
97
+
98
+ tr.appendChild(td);
99
+ });
100
+
101
+ return tr;
102
+ },
103
+
104
+ hRow: function(cells, type){
105
+ /* Return new <th> */
106
+ return util.row(cells, type, 'th');
107
+ },
108
+
109
+ table: function(headings, type){
110
+
111
+ headings = headings || [];
112
+
113
+ /* Creates new table: */
114
+ var attrs = {
115
+ thead: {
116
+ style:util.getStyles('thead',type)
117
+ },
118
+ tbody: {
119
+ style:util.getStyles('tbody',type)
120
+ },
121
+ table: {
122
+ style:util.getStyles('table',type)
123
+ }
124
+ },
125
+ tbl = util.el('table', attrs.table),
126
+ thead = util.el('thead', attrs.thead),
127
+ tbody = util.el('tbody', attrs.tbody);
128
+
129
+ if (headings.length) {
130
+ tbl.appendChild(thead);
131
+ thead.appendChild( util.hRow(headings, type) );
132
+ }
133
+ tbl.appendChild(tbody);
134
+
135
+ return {
136
+ /* Facade for dealing with table/tbody
137
+ Actual table node is this.node: */
138
+ node: tbl,
139
+ tbody: tbody,
140
+ thead: thead,
141
+ appendChild: function(node) {
142
+ this.tbody.appendChild(node);
143
+ },
144
+ addRow: function(cells, _type, cellType){
145
+ this.appendChild(util.row.call(util, cells, (_type || type), cellType));
146
+ return this;
147
+ }
148
+ };
149
+ },
150
+
151
+ shorten: function(str) {
152
+ var max = 40;
153
+ str = str.replace(/^\s\s*|\s\s*$|\n/g,'');
154
+ return str.length > max ? (str.substring(0, max-1) + '...') : str;
155
+ },
156
+
157
+ htmlentities: function(str) {
158
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
159
+ },
160
+
161
+ merge: function(target, source) {
162
+
163
+ /* Merges two (or more) objects,
164
+ giving the last one precedence */
165
+
166
+ if ( typeof target !== 'object' ) {
167
+ target = {};
168
+ }
169
+
170
+ for (var property in source) {
171
+
172
+ if ( source.hasOwnProperty(property) ) {
173
+
174
+ var sourceProperty = source[ property ];
175
+
176
+ if ( typeof sourceProperty === 'object' ) {
177
+ target[ property ] = util.merge( target[ property ], sourceProperty );
178
+ continue;
179
+ }
180
+
181
+ target[ property ] = sourceProperty;
182
+
183
+ }
184
+
185
+ }
186
+
187
+ for (var a = 2, l = arguments.length; a < l; a++) {
188
+ util.merge(target, arguments[a]);
189
+ }
190
+
191
+ return target;
192
+ },
193
+
194
+ count: function(arr, item) {
195
+ var count = 0;
196
+ for (var i = 0, l = arr.length; i< l; i++) {
197
+ if (arr[i] === item) {
198
+ count++;
199
+ }
200
+ }
201
+ return count;
202
+ },
203
+
204
+ thead: function(tbl) {
205
+ return tbl.getElementsByTagName('thead')[0];
206
+ },
207
+
208
+ forEach: function(arr, fn) {
209
+
210
+ /* Helper: iteration */
211
+ var len = arr.length, index = -1;
212
+
213
+ while (len > ++index) {
214
+ if(fn( arr[index], index, arr ) === false) {
215
+ break;
216
+ }
217
+ }
218
+
219
+ return true;
220
+ },
221
+
222
+ type: function(v){
223
+ try {
224
+ /* Returns type, e.g. "string", "number", "array" etc.
225
+ Note, this is only used for precise typing. */
226
+ if (v === null) { return 'null'; }
227
+ if (v === undefined) { return 'undefined'; }
228
+ var oType = Object.prototype.toString.call(v).match(/\s(.+?)\]/)[1].toLowerCase();
229
+ if (v.nodeType) {
230
+ if (v.nodeType === 1) {
231
+ return 'domelement';
232
+ }
233
+ return 'domnode';
234
+ }
235
+ if (/^(string|number|array|regexp|function|date|boolean)$/.test(oType)) {
236
+ return oType;
237
+ }
238
+ if (typeof v === 'object') {
239
+ return v.jquery && typeof v.jquery === 'string' ? 'jquery' : 'object';
240
+ }
241
+ if (v === window || v === document) {
242
+ return 'object';
243
+ }
244
+ return 'default';
245
+ } catch(e) {
246
+ return 'default';
247
+ }
248
+ },
249
+
250
+ within: function(ref) {
251
+ /* Check existence of a val within an object
252
+ RETURNS KEY */
253
+ return {
254
+ is: function(o) {
255
+ for (var i in ref) {
256
+ if (ref[i] === o) {
257
+ return i;
258
+ }
259
+ }
260
+ return '';
261
+ }
262
+ };
263
+ },
264
+
265
+ common: {
266
+ circRef: function(obj, key, settings) {
267
+ return util.expander(
268
+ '[POINTS BACK TO <strong>' + (key) + '</strong>]',
269
+ 'Click to show this item anyway',
270
+ function() {
271
+ this.parentNode.appendChild( prettyPrintThis(obj,{maxDepth:1}) );
272
+ }
273
+ );
274
+ },
275
+ depthReached: function(obj, settings) {
276
+ return util.expander(
277
+ '[DEPTH REACHED]',
278
+ 'Click to show this item anyway',
279
+ function() {
280
+ try {
281
+ this.parentNode.appendChild( prettyPrintThis(obj,{maxDepth:1}) );
282
+ } catch(e) {
283
+ this.parentNode.appendChild(
284
+ util.table(['ERROR OCCURED DURING OBJECT RETRIEVAL'],'error').addRow([e.message]).node
285
+ );
286
+ }
287
+ }
288
+ );
289
+ }
290
+ },
291
+
292
+ getStyles: function(el, type) {
293
+ type = prettyPrintThis.settings.styles[type] || {};
294
+ return util.merge(
295
+ {}, prettyPrintThis.settings.styles['default'][el], type[el]
296
+ );
297
+ },
298
+
299
+ expander: function(text, title, clickFn) {
300
+ return util.el('a', {
301
+ innerHTML: util.shorten(text) + ' <b style="visibility:hidden;">[+]</b>',
302
+ title: title,
303
+ onmouseover: function() {
304
+ this.getElementsByTagName('b')[0].style.visibility = 'visible';
305
+ },
306
+ onmouseout: function() {
307
+ this.getElementsByTagName('b')[0].style.visibility = 'hidden';
308
+ },
309
+ onclick: function() {
310
+ this.style.display = 'none';
311
+ clickFn.call(this);
312
+ return false;
313
+ },
314
+ style: {
315
+ cursor: 'pointer'
316
+ }
317
+ });
318
+ },
319
+
320
+ stringify: function(obj) {
321
+
322
+ /* Bit of an ugly duckling!
323
+ - This fn returns an ATTEMPT at converting an object/array/anyType
324
+ into a string, kinda like a JSON-deParser
325
+ - This is used for when |settings.expanded === false| */
326
+
327
+ var type = util.type(obj),
328
+ str, first = true;
329
+ if ( type === 'array' ) {
330
+ str = '[';
331
+ util.forEach(obj, function(item,i){
332
+ str += (i===0?'':', ') + util.stringify(item);
333
+ });
334
+ return str + ']';
335
+ }
336
+ if (typeof obj === 'object') {
337
+ str = '{';
338
+ for (var i in obj){
339
+ if (obj.hasOwnProperty(i)) {
340
+ str += (first?'':', ') + i + ':' + util.stringify(obj[i]);
341
+ first = false;
342
+ }
343
+ }
344
+ return str + '}';
345
+ }
346
+ if (type === 'regexp') {
347
+ return '/' + obj.source + '/';
348
+ }
349
+ if (type === 'string') {
350
+ return '"' + obj.replace(/"/g,'\\"') + '"';
351
+ }
352
+ return obj.toString();
353
+ },
354
+
355
+ headerGradient: (function(){
356
+
357
+ var canvas = document.createElement('canvas');
358
+ if (!canvas.getContext) { return ''; }
359
+ var cx = canvas.getContext('2d');
360
+ canvas.height = 30;
361
+ canvas.width = 1;
362
+
363
+ var linearGrad = cx.createLinearGradient(0,0,0,30);
364
+ linearGrad.addColorStop(0,'rgba(0,0,0,0)');
365
+ linearGrad.addColorStop(1,'rgba(0,0,0,0.25)');
366
+
367
+ cx.fillStyle = linearGrad;
368
+ cx.fillRect(0,0,1,30);
369
+
370
+ var dataURL = canvas.toDataURL && canvas.toDataURL();
371
+ return 'url(' + (dataURL || '') + ')';
372
+
373
+ })()
374
+
375
+ };
376
+
377
+ // Main..
378
+ var prettyPrintThis = function(obj, options) {
379
+
380
+ /*
381
+ * obj :: Object to be printed
382
+ * options :: Options (merged with config)
383
+ */
384
+
385
+ options = options || {};
386
+
387
+ var settings = util.merge( {}, prettyPrintThis.config, options ),
388
+ container = util.el('div'),
389
+ config = prettyPrintThis.config,
390
+ currentDepth = 0,
391
+ stack = {},
392
+ hasRunOnce = false;
393
+
394
+ /* Expose per-call settings.
395
+ Note: "config" is overwritten (where necessary) by options/"settings"
396
+ So, if you need to access/change *DEFAULT* settings then go via ".config" */
397
+ prettyPrintThis.settings = settings;
398
+
399
+ var typeDealer = {
400
+ string : function(item){
401
+ return util.txt('"' + util.shorten(item.replace(/"/g,'\\"')) + '"');
402
+ },
403
+ number : function(item) {
404
+ return util.txt(item);
405
+ },
406
+ regexp : function(item) {
407
+
408
+ var miniTable = util.table(['RegExp',null], 'regexp');
409
+ var flags = util.table();
410
+ var span = util.expander(
411
+ '/' + item.source + '/',
412
+ 'Click to show more',
413
+ function() {
414
+ this.parentNode.appendChild(miniTable.node);
415
+ }
416
+ );
417
+
418
+ flags
419
+ .addRow(['g', item.global])
420
+ .addRow(['i', item.ignoreCase])
421
+ .addRow(['m', item.multiline]);
422
+
423
+ miniTable
424
+ .addRow(['source', '/' + item.source + '/'])
425
+ .addRow(['flags', flags.node])
426
+ .addRow(['lastIndex', item.lastIndex]);
427
+
428
+ return settings.expanded ? miniTable.node : span;
429
+ },
430
+ domelement : function(element, depth) {
431
+
432
+ var miniTable = util.table(['DOMElement',null], 'domelement'),
433
+ props = ['id', 'className', 'innerHTML', 'src', 'href'], elname = element.nodeName || '';
434
+
435
+ miniTable.addRow(['tag', '&lt;' + elname.toLowerCase() + '&gt;']);
436
+
437
+ util.forEach(props, function(prop){
438
+ if ( element[prop] ) {
439
+ miniTable.addRow([ prop, util.htmlentities(element[prop]) ]);
440
+ }
441
+ });
442
+
443
+ return settings.expanded ? miniTable.node : util.expander(
444
+ 'DOMElement (' + elname.toLowerCase() + ')',
445
+ 'Click to show more',
446
+ function() {
447
+ this.parentNode.appendChild(miniTable.node);
448
+ }
449
+ );
450
+ },
451
+ domnode : function(node){
452
+
453
+ /* Deals with all DOMNodes that aren't elements (nodeType !== 1) */
454
+ var miniTable = util.table(['DOMNode',null], 'domelement'),
455
+ data = util.htmlentities( (node.data || 'UNDEFINED').replace(/\n/g,'\\n') );
456
+ miniTable
457
+ .addRow(['nodeType', node.nodeType + ' (' + node.nodeName + ')'])
458
+ .addRow(['data', data]);
459
+
460
+ return settings.expanded ? miniTable.node : util.expander(
461
+ 'DOMNode',
462
+ 'Click to show more',
463
+ function() {
464
+ this.parentNode.appendChild(miniTable.node);
465
+ }
466
+ );
467
+ },
468
+ jquery : function(obj, depth, key) {
469
+ return typeDealer['array'](obj, depth, key, true);
470
+ },
471
+ object : function(obj, depth, key) {
472
+
473
+ /* Checking depth + circular refs */
474
+ /* Note, check for circular refs before depth; just makes more sense */
475
+ var stackKey = util.within(stack).is(obj);
476
+ if ( stackKey ) {
477
+ return util.common.circRef(obj, stackKey, settings);
478
+ }
479
+ stack[key||'TOP'] = obj;
480
+ if (depth === settings.maxDepth) {
481
+ return util.common.depthReached(obj, settings);
482
+ }
483
+
484
+ var table = util.table(['Object', null],'object'),
485
+ isEmpty = true;
486
+
487
+ for (var i in obj) {
488
+ if (!obj.hasOwnProperty || obj.hasOwnProperty(i)) {
489
+ var item = obj[i],
490
+ type = util.type(item);
491
+ isEmpty = false;
492
+ try {
493
+ table.addRow([i, typeDealer[ type ](item, depth+1, i)], type);
494
+ } catch(e) {
495
+ /* Security errors are thrown on certain Window/DOM properties */
496
+ if (window.console && window.console.log) {
497
+ console.log(e.message);
498
+ }
499
+ }
500
+ }
501
+ }
502
+
503
+ if (isEmpty) {
504
+ table.addRow(['<small>[empty]</small>']);
505
+ } else {
506
+ table.thead.appendChild(
507
+ util.hRow(['key','value'], 'colHeader')
508
+ );
509
+ }
510
+
511
+ var ret = (settings.expanded || hasRunOnce) ? table.node : util.expander(
512
+ util.stringify(obj),
513
+ 'Click to show more',
514
+ function() {
515
+ this.parentNode.appendChild(table.node);
516
+ }
517
+ );
518
+
519
+ hasRunOnce = true;
520
+
521
+ return ret;
522
+
523
+ },
524
+ array : function(arr, depth, key, jquery) {
525
+
526
+ /* Checking depth + circular refs */
527
+ /* Note, check for circular refs before depth; just makes more sense */
528
+ var stackKey = util.within(stack).is(arr);
529
+ if ( stackKey ) {
530
+ return util.common.circRef(arr, stackKey);
531
+ }
532
+ stack[key||'TOP'] = arr;
533
+ if (depth === settings.maxDepth) {
534
+ return util.common.depthReached(arr);
535
+ }
536
+
537
+ /* Accepts a table and modifies it */
538
+ var me = jquery ? 'jQuery' : 'Array', table = util.table([me + '(' + arr.length + ')', null], jquery ? 'jquery' : me.toLowerCase()),
539
+ isEmpty = true;
540
+
541
+ if (jquery){
542
+ table.addRow(['selector',arr.selector]);
543
+ }
544
+
545
+ util.forEach(arr, function(item,i){
546
+ isEmpty = false;
547
+ table.addRow([i, typeDealer[ util.type(item) ](item, depth+1, i)]);
548
+ });
549
+
550
+ if (!jquery){
551
+ if (isEmpty) {
552
+ table.addRow(['<small>[empty]</small>']);
553
+ } else {
554
+ table.thead.appendChild( util.hRow(['index','value'], 'colHeader') );
555
+ }
556
+ }
557
+
558
+ return settings.expanded ? table.node : util.expander(
559
+ util.stringify(arr),
560
+ 'Click to show more',
561
+ function() {
562
+ this.parentNode.appendChild(table.node);
563
+ }
564
+ );
565
+
566
+ },
567
+ 'function' : function(fn, depth, key) {
568
+
569
+ /* Checking JUST circular refs */
570
+ var stackKey = util.within(stack).is(fn);
571
+ if ( stackKey ) { return util.common.circRef(fn, stackKey); }
572
+ stack[key||'TOP'] = fn;
573
+
574
+ var miniTable = util.table(['Function',null], 'function'),
575
+ argsTable = util.table(['Arguments']),
576
+ args = fn.toString().match(/\((.+?)\)/),
577
+ body = fn.toString().match(/\(.*?\)\s+?\{?([\S\s]+)/)[1].replace(/\}?$/,'');
578
+
579
+ miniTable
580
+ .addRow(['arguments', args ? args[1].replace(/[^\w_,\s]/g,'') : '<small>[none/native]</small>'])
581
+ .addRow(['body', body]);
582
+
583
+ return settings.expanded ? miniTable.node : util.expander(
584
+ 'function(){...}',
585
+ 'Click to see more about this function.',
586
+ function(){
587
+ this.parentNode.appendChild(miniTable.node);
588
+ }
589
+ );
590
+ },
591
+ 'date' : function(date) {
592
+
593
+ var miniTable = util.table(['Date',null], 'date'),
594
+ sDate = date.toString().split(/\s/);
595
+
596
+ /* TODO: Make this work well in IE! */
597
+ miniTable
598
+ .addRow(['Time', sDate[4]])
599
+ .addRow(['Date', sDate.slice(0,4).join('-')]);
600
+
601
+ return settings.expanded ? miniTable.node : util.expander(
602
+ 'Date (timestamp): ' + (+date),
603
+ 'Click to see a little more info about this date',
604
+ function() {
605
+ this.parentNode.appendChild(miniTable.node);
606
+ }
607
+ );
608
+
609
+ },
610
+ 'boolean' : function(bool) {
611
+ return util.txt( bool.toString().toUpperCase() );
612
+ },
613
+ 'undefined' : function() {
614
+ return util.txt('UNDEFINED');
615
+ },
616
+ 'null' : function() {
617
+ return util.txt('NULL');
618
+ },
619
+ 'default' : function() {
620
+ /* When a type cannot be found */
621
+ return util.txt('prettyPrint: TypeNotFound Error');
622
+ }
623
+ };
624
+
625
+ container.appendChild( typeDealer[ (settings.forceObject) ? 'object' : util.type(obj) ](obj, currentDepth) );
626
+
627
+ return container;
628
+
629
+ };
630
+
631
+ /* Configuration */
632
+
633
+ /* All items can be overwridden by passing an
634
+ "options" object when calling prettyPrint */
635
+ prettyPrintThis.config = {
636
+
637
+ /* Try setting this to false to save space */
638
+ expanded: true,
639
+
640
+ forceObject: false,
641
+ maxDepth: 3,
642
+ styles: {
643
+ array: {
644
+ th: {
645
+ backgroundColor: '#6DBD2A',
646
+ color: 'white'
647
+ }
648
+ },
649
+ 'function': {
650
+ th: {
651
+ backgroundColor: '#D82525'
652
+ }
653
+ },
654
+ regexp: {
655
+ th: {
656
+ backgroundColor: '#E2F3FB',
657
+ color: '#000'
658
+ }
659
+ },
660
+ object: {
661
+ th: {
662
+ backgroundColor: '#1F96CF'
663
+ }
664
+ },
665
+ jquery : {
666
+ th: {
667
+ backgroundColor: '#FBF315'
668
+ }
669
+ },
670
+ error: {
671
+ th: {
672
+ backgroundColor: 'red',
673
+ color: 'yellow'
674
+ }
675
+ },
676
+ domelement: {
677
+ th: {
678
+ backgroundColor: '#F3801E'
679
+ }
680
+ },
681
+ date: {
682
+ th: {
683
+ backgroundColor: '#A725D8'
684
+ }
685
+ },
686
+ colHeader: {
687
+ th: {
688
+ backgroundColor: '#EEE',
689
+ color: '#000',
690
+ textTransform: 'uppercase'
691
+ }
692
+ },
693
+ 'default': {
694
+ table: {
695
+ borderCollapse: 'collapse',
696
+ width: '100%'
697
+ },
698
+ td: {
699
+ padding: '5px',
700
+ fontSize: '12px',
701
+ backgroundColor: '#FFF',
702
+ color: '#222',
703
+ border: '1px solid #000',
704
+ verticalAlign: 'top',
705
+ fontFamily: '"Consolas","Lucida Console",Courier,mono',
706
+ whiteSpace: 'nowrap'
707
+ },
708
+ td_hover: {
709
+ /* Styles defined here will apply to all tr:hover > td,
710
+ - Be aware that "inheritable" properties (e.g. fontWeight) WILL BE INHERITED */
711
+ },
712
+ th: {
713
+ padding: '5px',
714
+ fontSize: '12px',
715
+ backgroundColor: '#222',
716
+ color: '#EEE',
717
+ textAlign: 'left',
718
+ border: '1px solid #000',
719
+ verticalAlign: 'top',
720
+ fontFamily: '"Consolas","Lucida Console",Courier,mono',
721
+ backgroundImage: util.headerGradient,
722
+ backgroundRepeat: 'repeat-x'
723
+ }
724
+ }
725
+ }
726
+ };
727
+
728
+ return prettyPrintThis;
729
+
730
+ })();