Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / docs / source / debug.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.1
11  * Copyright(c) 2006-2010 Sencha Inc.
12  * licensing@sencha.com
13  * http://www.sencha.com/license
14  */
15 Ext.debug = {};
16
17 (function(){
18
19 var cp;
20
21 function createConsole(){
22
23     var scriptPanel = new Ext.debug.ScriptsPanel();
24     var logView = new Ext.debug.LogPanel();
25     var tree = new Ext.debug.DomTree();
26     var compInspector = new Ext.debug.ComponentInspector();
27     var compInfoPanel = new Ext.debug.ComponentInfoPanel();
28     var storeInspector = new Ext.debug.StoreInspector();
29     var objInspector = new Ext.debug.ObjectInspector();
30
31     var tabs = new Ext.TabPanel({
32         activeTab: 0,
33         border: false,
34         tabPosition: 'bottom',
35         items: [{
36             title: 'Debug Console',
37             layout:'border',
38             items: [logView, scriptPanel]
39         },{
40             title: 'HTML Inspector',
41             layout:'border',
42             items: [tree]
43         },{
44             title: 'Component Inspector',
45             layout: 'border',
46             items: [compInspector,compInfoPanel]
47         },{
48             title: 'Object Inspector',
49             layout: 'border',
50             items: [objInspector]
51         },{
52             title: 'Data Stores',
53             layout: 'border',
54             items: [storeInspector]
55         }]
56     });
57
58     cp = new Ext.Panel({
59         id: 'x-debug-browser',
60         title: 'Console',
61         collapsible: true,
62         animCollapse: false,
63         style: 'position:absolute;left:0;bottom:0;z-index:101',
64         height:200,
65         logView: logView,
66         layout: 'fit',
67
68         tools:[{
69             id: 'close',
70             handler: function(){
71                 cp.destroy();
72                 cp = null;
73                 Ext.EventManager.removeResizeListener(handleResize);
74             }
75         }],
76
77         items: tabs
78     });
79
80     cp.render(Ext.getBody());
81
82     cp.resizer = new Ext.Resizable(cp.el, {
83         minHeight:50,
84         handles: "n",
85         pinned: true,
86         transparent:true,
87         resizeElement : function(){
88             var box = this.proxy.getBox();
89             this.proxy.hide();
90             cp.setHeight(box.height);
91             return box;
92         }
93     });
94
95 //     function handleResize(){
96 //         cp.setWidth(Ext.getBody().getViewSize().width);
97 //     }
98 //     Ext.EventManager.onWindowResize(handleResize);
99 //
100 //     handleResize();
101
102     function handleResize(){
103         var b = Ext.getBody();
104         var size = b.getViewSize();
105         if(size.height < b.dom.scrollHeight) {
106             size.width -= 18;
107         }
108         cp.setWidth(size.width);
109     }
110     Ext.EventManager.onWindowResize(handleResize);
111     handleResize();
112 }
113
114
115 Ext.apply(Ext, {
116     log : function(){
117         if(!cp){
118             createConsole();
119         }
120         cp.logView.log.apply(cp.logView, arguments);
121     },
122
123     logf : function(format, arg1, arg2, etc){
124         Ext.log(String.format.apply(String, arguments));
125     },
126
127     dump : function(o){
128         if(typeof o == 'string' || typeof o == 'number' || typeof o == 'undefined' || Ext.isDate(o)){
129             Ext.log(o);
130         }else if(!o){
131             Ext.log("null");
132         }else if(typeof o != "object"){
133             Ext.log('Unknown return type');
134         }else if(Ext.isArray(o)){
135             Ext.log('['+o.join(',')+']');
136         }else{
137             var b = ["{\n"];
138             for(var key in o){
139                 var to = typeof o[key];
140                 if(to != "function" && to != "object"){
141                     b.push(String.format("  {0}: {1},\n", key, o[key]));
142                 }
143             }
144             var s = b.join("");
145             if(s.length > 3){
146                 s = s.substr(0, s.length-2);
147             }
148             Ext.log(s + "\n}");
149         }
150     },
151
152     _timers : {},
153
154     time : function(name){
155         name = name || "def";
156         Ext._timers[name] = new Date().getTime();
157     },
158
159     timeEnd : function(name, printResults){
160         var t = new Date().getTime();
161         name = name || "def";
162         var v = String.format("{0} ms", t-Ext._timers[name]);
163         Ext._timers[name] = new Date().getTime();
164         if(printResults !== false){
165             Ext.log('Timer ' + (name == "def" ? v : name + ": " + v));
166         }
167         return v;
168     }
169 });
170
171 })();
172
173
174 Ext.debug.ScriptsPanel = Ext.extend(Ext.Panel, {
175     id:'x-debug-scripts',
176     region: 'east',
177     minWidth: 200,
178     split: true,
179     width: 350,
180     border: false,
181     layout:'anchor',
182     style:'border-width:0 0 0 1px;',
183
184     initComponent : function(){
185
186         this.scriptField = new Ext.form.TextArea({
187             anchor: '100% -26',
188             style:'border-width:0;'
189         });
190
191         this.trapBox = new Ext.form.Checkbox({
192             id: 'console-trap',
193             boxLabel: 'Trap Errors',
194             checked: true
195         });
196
197         this.toolbar = new Ext.Toolbar([{
198                 text: 'Run',
199                 scope: this,
200                 handler: this.evalScript
201             },{
202                 text: 'Clear',
203                 scope: this,
204                 handler: this.clear
205             },
206             '->',
207             this.trapBox,
208             ' ', ' '
209         ]);
210
211         this.items = [this.toolbar, this.scriptField];
212
213         Ext.debug.ScriptsPanel.superclass.initComponent.call(this);
214     },
215
216     evalScript : function(){
217         var s = this.scriptField.getValue();
218         if(this.trapBox.getValue()){
219             try{
220                 var rt = eval(s);
221                 Ext.dump(rt === undefined? '(no return)' : rt);
222             }catch(e){
223                 Ext.log(e.message || e.descript);
224             }
225         }else{
226             var rt = eval(s);
227             Ext.dump(rt === undefined? '(no return)' : rt);
228         }
229     },
230
231     clear : function(){
232         this.scriptField.setValue('');
233         this.scriptField.focus();
234     }
235
236 });
237
238 Ext.debug.LogPanel = Ext.extend(Ext.Panel, {
239     autoScroll: true,
240     region: 'center',
241     border: false,
242     style:'border-width:0 1px 0 0',
243
244     log : function(){
245         var markup = [  '<div style="padding:5px !important;border-bottom:1px solid #ccc;">',
246                     Ext.util.Format.htmlEncode(Array.prototype.join.call(arguments, ', ')).replace(/\n/g, '<br/>').replace(/\s/g, '&#160;'),
247                     '</div>'].join(''),
248             bd = this.body.dom;
249
250         this.body.insertHtml('beforeend', markup);
251         bd.scrollTop = bd.scrollHeight;
252     },
253
254     clear : function(){
255         this.body.update('');
256         this.body.dom.scrollTop = 0;
257     }
258 });
259
260 Ext.debug.DomTree = Ext.extend(Ext.tree.TreePanel, {
261     enableDD:false ,
262     lines:false,
263     rootVisible:false,
264     animate:false,
265     hlColor:'ffff9c',
266     autoScroll: true,
267     region:'center',
268     border:false,
269
270     initComponent : function(){
271
272
273         Ext.debug.DomTree.superclass.initComponent.call(this);
274
275         // tree related stuff
276         var styles = false, hnode;
277         var nonSpace = /^\s*$/;
278         var html = Ext.util.Format.htmlEncode;
279         var ellipsis = Ext.util.Format.ellipsis;
280         var styleRe = /\s?([a-z\-]*)\:([^;]*)(?:[;\s\n\r]*)/gi;
281
282         function findNode(n){
283             if(!n || n.nodeType != 1 || n == document.body || n == document){
284                 return false;
285             }
286             var pn = [n], p = n;
287             while((p = p.parentNode) && p.nodeType == 1 && p.tagName.toUpperCase() != 'HTML'){
288                 pn.unshift(p);
289             }
290             var cn = hnode;
291             for(var i = 0, len = pn.length; i < len; i++){
292                 cn.expand();
293                 cn = cn.findChild('htmlNode', pn[i]);
294                 if(!cn){ // in this dialog?
295                     return false;
296                 }
297             }
298             cn.select();
299             var a = cn.ui.anchor;
300             this.getTreeEl().dom.scrollTop = Math.max(0 ,a.offsetTop-10);
301             //treeEl.dom.scrollLeft = Math.max(0 ,a.offsetLeft-10); no likey
302             cn.highlight();
303             return true;
304         }
305
306         function nodeTitle(n){
307             var s = n.tagName;
308             if(n.id){
309                 s += '#'+n.id;
310             }else if(n.className){
311                 s += '.'+n.className;
312             }
313             return s;
314         }
315
316         /*
317         function onNodeSelect(t, n, last){
318             return;
319             if(last && last.unframe){
320                 last.unframe();
321             }
322             var props = {};
323             if(n && n.htmlNode){
324                 if(frameEl.pressed){
325                     n.frame();
326                 }
327                 if(inspecting){
328                     return;
329                 }
330                 addStyle.enable();
331                 reload.setDisabled(n.leaf);
332                 var dom = n.htmlNode;
333                 stylePanel.setTitle(nodeTitle(dom));
334                 if(styles && !showAll.pressed){
335                     var s = dom.style ? dom.style.cssText : '';
336                     if(s){
337                         var m;
338                         while ((m = styleRe.exec(s)) != null){
339                             props[m[1].toLowerCase()] = m[2];
340                         }
341                     }
342                 }else if(styles){
343                     var cl = Ext.debug.cssList;
344                     var s = dom.style, fly = Ext.fly(dom);
345                     if(s){
346                         for(var i = 0, len = cl.length; i<len; i++){
347                             var st = cl[i];
348                             var v = s[st] || fly.getStyle(st);
349                             if(v != undefined && v !== null && v !== ''){
350                                 props[st] = v;
351                             }
352                         }
353                     }
354                 }else{
355                     for(var a in dom){
356                         var v = dom[a];
357                         if((isNaN(a+10)) && v != undefined && v !== null && v !== '' && !(Ext.isGecko && a[0] == a[0].toUpperCase())){
358                             props[a] = v;
359                         }
360                     }
361                 }
362             }else{
363                 if(inspecting){
364                     return;
365                 }
366                 addStyle.disable();
367                 reload.disabled();
368             }
369             stylesGrid.setSource(props);
370             stylesGrid.treeNode = n;
371             stylesGrid.view.fitColumns();
372         }
373         */
374
375         this.loader = new Ext.tree.TreeLoader();
376         this.loader.load = function(n, cb){
377             var isBody = n.htmlNode == document.body;
378             var cn = n.htmlNode.childNodes;
379             for(var i = 0, c; c = cn[i]; i++){
380                 if(isBody && c.id == 'x-debug-browser'){
381                     continue;
382                 }
383                 if(c.nodeType == 1){
384                     n.appendChild(new Ext.debug.HtmlNode(c));
385                 }else if(c.nodeType == 3 && !nonSpace.test(c.nodeValue)){
386                     n.appendChild(new Ext.tree.TreeNode({
387                         text:'<em>' + ellipsis(html(String(c.nodeValue)), 35) + '</em>',
388                         cls: 'x-tree-noicon'
389                     }));
390                 }
391             }
392             cb();
393         };
394
395         //tree.getSelectionModel().on('selectionchange', onNodeSelect, null, {buffer:250});
396
397         this.root = this.setRootNode(new Ext.tree.TreeNode('Ext'));
398
399         hnode = this.root.appendChild(new Ext.debug.HtmlNode(
400                 document.getElementsByTagName('html')[0]
401         ));
402
403     }
404 });
405
406 Ext.debug.ComponentNodeUI = Ext.extend(Ext.tree.TreeNodeUI,{
407     onOver : function(e){
408         Ext.debug.ComponentNodeUI.superclass.onOver.call(this);
409         var cmp = this.node.attributes.component;
410         if (cmp.el && cmp.el.mask && cmp.id !='x-debug-browser') {
411             try { // Oddly bombs on some elements in IE, gets any we care about though
412                 cmp.el.mask();
413             } catch(e) {}
414         }
415     },
416
417     onOut : function(e){
418         Ext.debug.ComponentNodeUI.superclass.onOut.call(this);
419         var cmp = this.node.attributes.component;
420         if (cmp.el && cmp.el.unmask && cmp.id !='x-debug-browser') {
421             try {
422                 cmp.el.unmask();
423             } catch(e) {}
424         }
425     }
426 });
427
428 Ext.debug.ComponentInspector = Ext.extend(Ext.tree.TreePanel, {
429     enableDD:false ,
430     lines:false,
431     rootVisible:false,
432     animate:false,
433     hlColor:'ffff9c',
434     autoScroll: true,
435     region:'center',
436     border:false,
437
438     initComponent : function(){
439         this.loader = new Ext.tree.TreeLoader();
440         this.bbar = new Ext.Toolbar([{
441             text: 'Refresh',
442             handler: this.refresh,
443             scope: this
444         }]);
445         Ext.debug.ComponentInspector.superclass.initComponent.call(this);
446
447         this.root = this.setRootNode(new Ext.tree.TreeNode({
448             text: 'Ext Components',
449             component: Ext.ComponentMgr.all,
450             leaf: false
451         }));
452         this.parseRootNode();
453
454         this.on('click', this.onClick, this);
455     },
456
457     createNode: function(n,c) {
458         var leaf = (c.items && c.items.length > 0);
459         return n.appendChild(new Ext.tree.TreeNode({
460             text: c.id + (c.getXType() ? ' [ ' + c.getXType() + ' ]': '' ),
461             component: c,
462             uiProvider:Ext.debug.ComponentNodeUI,
463             leaf: !leaf
464         }));
465     },
466
467     parseChildItems: function(n) {
468         var cn = n.attributes.component.items;
469         if (cn) {
470             for (var i = 0;i < cn.length; i++) {
471                 var c = cn.get(i);
472                 if (c.id != this.id && c.id != this.bottomToolbar.id) {
473                     var newNode = this.createNode(n,c);
474                     if (!newNode.leaf) {
475                         this.parseChildItems(newNode);
476                     }
477                 }
478             }
479         }
480     },
481
482     parseRootNode: function() {
483         var n = this.root;
484         var cn = n.attributes.component.items;
485         for (var i = 0,c;c = cn[i];i++) {
486             if (c.id != this.id && c.id != this.bottomToolbar.id) {
487                 if (!c.ownerCt) {
488                     var newNode = this.createNode(n,c);
489                     if (!newNode.leaf) {
490                         this.parseChildItems(newNode);
491                     }
492                 }
493             }
494         }
495     },
496
497     onClick: function(node, e) {
498         var oi = Ext.getCmp('x-debug-objinspector');
499         oi.refreshNodes(node.attributes.component);
500         oi.ownerCt.show();
501     },
502
503     refresh: function() {
504         while (this.root.firstChild) {
505             this.root.removeChild(this.root.firstChild);
506         }
507         this.parseRootNode();
508         var ci = Ext.getCmp('x-debug-compinfo');
509         if (ci) {
510             ci.message('refreshed component tree - '+Ext.ComponentMgr.all.length);
511         }
512     }
513 });
514
515 Ext.debug.ComponentInfoPanel = Ext.extend(Ext.Panel,{
516     id:'x-debug-compinfo',
517     region: 'east',
518     minWidth: 200,
519     split: true,
520     width: 350,
521     border: false,
522     autoScroll: true,
523     layout:'anchor',
524     style:'border-width:0 0 0 1px;',
525
526     initComponent: function() {
527         this.watchBox = new Ext.form.Checkbox({
528             id: 'x-debug-watchcomp',
529             boxLabel: 'Watch ComponentMgr',
530             listeners: {
531                 check: function(cb, val) {
532                     if (val) {
533                         Ext.ComponentMgr.all.on('add', this.onAdd, this);
534                         Ext.ComponentMgr.all.on('remove', this.onRemove, this);
535                     } else {
536                         Ext.ComponentMgr.all.un('add', this.onAdd, this);
537                         Ext.ComponentMgr.all.un('remove', this.onRemove, this);
538                     }
539                 },
540                 scope: this
541             }
542         });
543
544         this.tbar = new Ext.Toolbar([{
545             text: 'Clear',
546             handler: this.clear,
547             scope: this
548         },'->',this.watchBox
549         ]);
550         Ext.debug.ComponentInfoPanel.superclass.initComponent.call(this);
551     },
552
553     onAdd: function(i, o, key) {
554         var markup = ['<div style="padding:5px !important;border-bottom:1px solid #ccc;">',
555                     'Added: '+o.id,
556                     '</div>'].join('');
557         this.insertMarkup(markup);
558     },
559
560     onRemove: function(o, key) {
561         var markup = ['<div style="padding:5px !important;border-bottom:1px solid #ccc;">',
562                     'Removed: '+o.id,
563                     '</div>'].join('');
564         this.insertMarkup(markup);
565     },
566
567     message: function(msg) {
568         var markup = ['<div style="padding:5px !important;border-bottom:1px solid #ccc;">',
569                     msg,
570                     '</div>'].join('');
571         this.insertMarkup(markup);
572     },
573     insertMarkup: function(markup) {
574         this.body.insertHtml('beforeend', markup);
575         this.body.scrollTo('top', 100000);
576     },
577     clear : function(){
578         this.body.update('');
579         this.body.dom.scrollTop = 0;
580     }
581 });
582
583 Ext.debug.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
584     focus: Ext.emptyFn, // prevent odd scrolling behavior
585
586     renderElements : function(n, a, targetNode, bulkRender){
587         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
588
589         var t = n.getOwnerTree();
590         var cols = t.columns;
591         var bw = t.borderWidth;
592         var c = cols[0];
593
594         var buf = [
595              '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',
596                 '<div class="x-tree-col" style="width:',c.width-bw,'px;">',
597                     '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
598                     '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow"/>',
599                     '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on"/>',
600                     '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',
601                     a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',
602                     '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",
603                 "</div>"];
604          for(var i = 1, len = cols.length; i < len; i++){
605              c = cols[i];
606
607              buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',
608                         '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",
609                       "</div>");
610          }
611          buf.push(
612             '<div class="x-clear"></div></div>',
613             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
614             "</li>");
615
616         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
617             this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
618                                 n.nextSibling.ui.getEl(), buf.join(""));
619         }else{
620             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
621         }
622
623         this.elNode = this.wrap.childNodes[0];
624         this.ctNode = this.wrap.childNodes[1];
625         var cs = this.elNode.firstChild.childNodes;
626         this.indentNode = cs[0];
627         this.ecNode = cs[1];
628         this.iconNode = cs[2];
629         this.anchor = cs[3];
630         this.textNode = cs[3].firstChild;
631     }
632 });
633
634 Ext.debug.ObjectInspector = Ext.extend(Ext.tree.TreePanel, {
635     id: 'x-debug-objinspector',
636     enableDD:false ,
637     lines:false,
638     rootVisible:false,
639     animate:false,
640     hlColor:'ffff9c',
641     autoScroll: true,
642     region:'center',
643     border:false,
644     lines:false,
645     borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
646     cls:'x-column-tree',
647
648     initComponent : function(){
649         this.showFunc = false;
650         this.toggleFunc = function() {
651             this.showFunc = !this.showFunc;
652             this.refreshNodes(this.currentObject);
653         };
654         this.bbar = new Ext.Toolbar([{
655             text: 'Show Functions',
656             enableToggle: true,
657             pressed: false,
658             handler: this.toggleFunc,
659             scope: this
660         }]);
661
662         Ext.apply(this,{
663             title: ' ',
664             loader: new Ext.tree.TreeLoader(),
665             columns:[{
666                 header:'Property',
667                 width: 300,
668                 dataIndex:'name'
669             },{
670                 header:'Value',
671                 width: 900,
672                 dataIndex:'value'
673             }]
674         });
675
676         Ext.debug.ObjectInspector.superclass.initComponent.call(this);
677
678         this.root = this.setRootNode(new Ext.tree.TreeNode({
679             text: 'Dummy Node',
680             leaf: false
681         }));
682
683         if (this.currentObject) {
684             this.parseNodes();
685         }
686     },
687
688     refreshNodes: function(newObj) {
689         this.currentObject = newObj;
690         var node = this.root;
691         while(node.firstChild){
692             node.removeChild(node.firstChild);
693         }
694         this.parseNodes();
695     },
696
697     parseNodes: function() {
698         for (var o in this.currentObject) {
699             if (!this.showFunc) {
700                 if (Ext.isFunction(this.currentObject[o])) {
701                     continue;
702                 }
703             }
704             this.createNode(o);
705         }
706     },
707
708     createNode: function(o) {
709         return this.root.appendChild(new Ext.tree.TreeNode({
710             name: o,
711             value: this.currentObject[o],
712             uiProvider:Ext.debug.ColumnNodeUI,
713             iconCls: 'x-debug-node',
714             leaf: true
715         }));
716     },
717
718     onRender : function(){
719         Ext.debug.ObjectInspector.superclass.onRender.apply(this, arguments);
720         this.headers = this.header.createChild({cls:'x-tree-headers'});
721
722         var cols = this.columns, c;
723         var totalWidth = 0;
724
725         for(var i = 0, len = cols.length; i < len; i++){
726              c = cols[i];
727              totalWidth += c.width;
728              this.headers.createChild({
729                  cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),
730                  cn: {
731                      cls:'x-tree-hd-text',
732                      html: c.header
733                  },
734                  style:'width:'+(c.width-this.borderWidth)+'px;'
735              });
736         }
737         this.headers.createChild({cls:'x-clear'});
738         // prevent floats from wrapping when clipped
739         this.headers.setWidth(totalWidth);
740         this.innerCt.setWidth(totalWidth);
741     }
742 });
743
744
745 Ext.debug.StoreInspector = Ext.extend(Ext.tree.TreePanel, {
746     enableDD:false ,
747     lines:false,
748     rootVisible:false,
749     animate:false,
750     hlColor:'ffff9c',
751     autoScroll: true,
752     region:'center',
753     border:false,
754
755     initComponent: function() {
756         this.bbar = new Ext.Toolbar([{
757             text: 'Refresh',
758             handler: this.refresh,
759             scope: this
760         }]);
761         Ext.debug.StoreInspector.superclass.initComponent.call(this);
762
763         this.root = this.setRootNode(new Ext.tree.TreeNode({
764             text: 'Data Stores',
765             leaf: false
766         }));
767         this.on('click', this.onClick, this);
768
769         this.parseStores();
770     },
771
772     parseStores: function() {
773         var cn = Ext.StoreMgr.items;
774         for (var i = 0,c;c = cn[i];i++) {
775             this.root.appendChild({
776                 text: c.storeId + ' - ' + c.totalLength + ' records',
777                 component: c,
778                 leaf: true
779             });
780         }
781     },
782
783     onClick: function(node, e) {
784         var oi = Ext.getCmp('x-debug-objinspector');
785         oi.refreshNodes(node.attributes.component);
786         oi.ownerCt.show();
787     },
788
789     refresh: function() {
790         while (this.root.firstChild) {
791             this.root.removeChild(this.root.firstChild);
792         }
793         this.parseStores();
794     }
795 });
796
797 // highly unusual class declaration
798 Ext.debug.HtmlNode = function(){
799     var html = Ext.util.Format.htmlEncode;
800     var ellipsis = Ext.util.Format.ellipsis;
801     var nonSpace = /^\s*$/;
802
803     var attrs = [
804         {n: 'id', v: 'id'},
805         {n: 'className', v: 'class'},
806         {n: 'name', v: 'name'},
807         {n: 'type', v: 'type'},
808         {n: 'src', v: 'src'},
809         {n: 'href', v: 'href'}
810     ];
811
812     function hasChild(n){
813         for(var i = 0, c; c = n.childNodes[i]; i++){
814             if(c.nodeType == 1){
815                 return true;
816             }
817         }
818         return false;
819     }
820
821     function renderNode(n, leaf){
822         var tag = n.tagName.toLowerCase();
823         var s = '&lt;' + tag;
824         for(var i = 0, len = attrs.length; i < len; i++){
825             var a = attrs[i];
826             var v = n[a.n];
827             if(v && !nonSpace.test(v)){
828                 s += ' ' + a.v + '=&quot;<i>' + html(v) +'</i>&quot;';
829             }
830         }
831         var style = n.style ? n.style.cssText : '';
832         if(style){
833             s += ' style=&quot;<i>' + html(style.toLowerCase()) +'</i>&quot;';
834         }
835         if(leaf && n.childNodes.length > 0){
836             s+='&gt;<em>' + ellipsis(html(String(n.innerHTML)), 35) + '</em>&lt;/'+tag+'&gt;';
837         }else if(leaf){
838             s += ' /&gt;';
839         }else{
840             s += '&gt;';
841         }
842         return s;
843     }
844
845     var HtmlNode = function(n){
846         var leaf = !hasChild(n);
847         this.htmlNode = n;
848         this.tagName = n.tagName.toLowerCase();
849         var attr = {
850             text : renderNode(n, leaf),
851             leaf : leaf,
852             cls: 'x-tree-noicon'
853         };
854         HtmlNode.superclass.constructor.call(this, attr);
855         this.attributes.htmlNode = n; // for searching
856         if(!leaf){
857             this.on('expand', this.onExpand,  this);
858             this.on('collapse', this.onCollapse,  this);
859         }
860     };
861
862
863     Ext.extend(HtmlNode, Ext.tree.AsyncTreeNode, {
864         cls: 'x-tree-noicon',
865         preventHScroll: true,
866         refresh : function(highlight){
867             var leaf = !hasChild(this.htmlNode);
868             this.setText(renderNode(this.htmlNode, leaf));
869             if(highlight){
870                 Ext.fly(this.ui.textNode).highlight();
871             }
872         },
873
874         onExpand : function(){
875             if(!this.closeNode && this.parentNode){
876                 this.closeNode = this.parentNode.insertBefore(new Ext.tree.TreeNode({
877                     text:'&lt;/' + this.tagName + '&gt;',
878                     cls: 'x-tree-noicon'
879                 }), this.nextSibling);
880             }else if(this.closeNode){
881                 this.closeNode.ui.show();
882             }
883         },
884
885         onCollapse : function(){
886             if(this.closeNode){
887                 this.closeNode.ui.hide();
888             }
889         },
890
891         render : function(bulkRender){
892             HtmlNode.superclass.render.call(this, bulkRender);
893         },
894
895         highlightNode : function(){
896             //Ext.fly(this.htmlNode).highlight();
897         },
898
899         highlight : function(){
900             //Ext.fly(this.ui.textNode).highlight();
901         },
902
903         frame : function(){
904             this.htmlNode.style.border = '1px solid #0000ff';
905             //this.highlightNode();
906         },
907
908         unframe : function(){
909             //Ext.fly(this.htmlNode).removeClass('x-debug-frame');
910             this.htmlNode.style.border = '';
911         }
912     });
913
914     return HtmlNode;
915 }();</pre>    
916 </body>
917 </html>