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