Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / debug.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 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
241         this.body.insertHtml('beforeend', markup);
242         this.body.scrollTo('top', 100000);
243     },
244
245     clear : function(){
246         this.body.update('');
247         this.body.dom.scrollTop = 0;
248     }
249 });
250
251 Ext.debug.DomTree = Ext.extend(Ext.tree.TreePanel, {
252     enableDD:false ,
253     lines:false,
254     rootVisible:false,
255     animate:false,
256     hlColor:'ffff9c',
257     autoScroll: true,
258     region:'center',
259     border:false,
260
261     initComponent : function(){
262
263
264         Ext.debug.DomTree.superclass.initComponent.call(this);
265
266         // tree related stuff
267         var styles = false, hnode;
268         var nonSpace = /^\s*$/;
269         var html = Ext.util.Format.htmlEncode;
270         var ellipsis = Ext.util.Format.ellipsis;
271         var styleRe = /\s?([a-z\-]*)\:([^;]*)(?:[;\s\n\r]*)/gi;
272
273         function findNode(n){
274             if(!n || n.nodeType != 1 || n == document.body || n == document){
275                 return false;
276             }
277             var pn = [n], p = n;
278             while((p = p.parentNode) && p.nodeType == 1 && p.tagName.toUpperCase() != 'HTML'){
279                 pn.unshift(p);
280             }
281             var cn = hnode;
282             for(var i = 0, len = pn.length; i < len; i++){
283                 cn.expand();
284                 cn = cn.findChild('htmlNode', pn[i]);
285                 if(!cn){ // in this dialog?
286                     return false;
287                 }
288             }
289             cn.select();
290             var a = cn.ui.anchor;
291             this.getTreeEl().dom.scrollTop = Math.max(0 ,a.offsetTop-10);
292             //treeEl.dom.scrollLeft = Math.max(0 ,a.offsetLeft-10); no likey
293             cn.highlight();
294             return true;
295         }
296
297         function nodeTitle(n){
298             var s = n.tagName;
299             if(n.id){
300                 s += '#'+n.id;
301             }else if(n.className){
302                 s += '.'+n.className;
303             }
304             return s;
305         }
306
307         /*
308         function onNodeSelect(t, n, last){
309             return;
310             if(last && last.unframe){
311                 last.unframe();
312             }
313             var props = {};
314             if(n && n.htmlNode){
315                 if(frameEl.pressed){
316                     n.frame();
317                 }
318                 if(inspecting){
319                     return;
320                 }
321                 addStyle.enable();
322                 reload.setDisabled(n.leaf);
323                 var dom = n.htmlNode;
324                 stylePanel.setTitle(nodeTitle(dom));
325                 if(styles && !showAll.pressed){
326                     var s = dom.style ? dom.style.cssText : '';
327                     if(s){
328                         var m;
329                         while ((m = styleRe.exec(s)) != null){
330                             props[m[1].toLowerCase()] = m[2];
331                         }
332                     }
333                 }else if(styles){
334                     var cl = Ext.debug.cssList;
335                     var s = dom.style, fly = Ext.fly(dom);
336                     if(s){
337                         for(var i = 0, len = cl.length; i<len; i++){
338                             var st = cl[i];
339                             var v = s[st] || fly.getStyle(st);
340                             if(v != undefined && v !== null && v !== ''){
341                                 props[st] = v;
342                             }
343                         }
344                     }
345                 }else{
346                     for(var a in dom){
347                         var v = dom[a];
348                         if((isNaN(a+10)) && v != undefined && v !== null && v !== '' && !(Ext.isGecko && a[0] == a[0].toUpperCase())){
349                             props[a] = v;
350                         }
351                     }
352                 }
353             }else{
354                 if(inspecting){
355                     return;
356                 }
357                 addStyle.disable();
358                 reload.disabled();
359             }
360             stylesGrid.setSource(props);
361             stylesGrid.treeNode = n;
362             stylesGrid.view.fitColumns();
363         }
364         */
365
366         this.loader = new Ext.tree.TreeLoader();
367         this.loader.load = function(n, cb){
368             var isBody = n.htmlNode == document.body;
369             var cn = n.htmlNode.childNodes;
370             for(var i = 0, c; c = cn[i]; i++){
371                 if(isBody && c.id == 'x-debug-browser'){
372                     continue;
373                 }
374                 if(c.nodeType == 1){
375                     n.appendChild(new Ext.debug.HtmlNode(c));
376                 }else if(c.nodeType == 3 && !nonSpace.test(c.nodeValue)){
377                     n.appendChild(new Ext.tree.TreeNode({
378                         text:'<em>' + ellipsis(html(String(c.nodeValue)), 35) + '</em>',
379                         cls: 'x-tree-noicon'
380                     }));
381                 }
382             }
383             cb();
384         };
385
386         //tree.getSelectionModel().on('selectionchange', onNodeSelect, null, {buffer:250});
387
388         this.root = this.setRootNode(new Ext.tree.TreeNode('Ext'));
389
390         hnode = this.root.appendChild(new Ext.debug.HtmlNode(
391                 document.getElementsByTagName('html')[0]
392         ));
393
394     }
395 });
396
397 Ext.debug.ComponentNodeUI = Ext.extend(Ext.tree.TreeNodeUI,{
398     onOver : function(e){
399         Ext.debug.ComponentNodeUI.superclass.onOver.call(this);
400         var cmp = this.node.attributes.component;
401         if (cmp.el && cmp.el.mask && cmp.id !='x-debug-browser') {
402             try { // Oddly bombs on some elements in IE, gets any we care about though
403                 cmp.el.mask();
404             } catch(e) {}
405         }
406     },
407
408     onOut : function(e){
409         Ext.debug.ComponentNodeUI.superclass.onOut.call(this);
410         var cmp = this.node.attributes.component;
411         if (cmp.el && cmp.el.unmask && cmp.id !='x-debug-browser') {
412             try {
413                 cmp.el.unmask();
414             } catch(e) {}
415         }
416     }
417 });
418
419 Ext.debug.ComponentInspector = Ext.extend(Ext.tree.TreePanel, {
420     enableDD:false ,
421     lines:false,
422     rootVisible:false,
423     animate:false,
424     hlColor:'ffff9c',
425     autoScroll: true,
426     region:'center',
427     border:false,
428
429     initComponent : function(){
430         this.loader = new Ext.tree.TreeLoader();
431         this.bbar = new Ext.Toolbar([{
432             text: 'Refresh',
433             handler: this.refresh,
434             scope: this
435         }]);
436         Ext.debug.ComponentInspector.superclass.initComponent.call(this);
437
438         this.root = this.setRootNode(new Ext.tree.TreeNode({
439             text: 'Ext Components',
440             component: Ext.ComponentMgr.all,
441             leaf: false
442         }));
443         this.parseRootNode();
444
445         this.on('click', this.onClick, this);
446     },
447
448     createNode: function(n,c) {
449         var leaf = (c.items && c.items.length > 0);
450         return n.appendChild(new Ext.tree.TreeNode({
451             text: c.id + (c.getXType() ? ' [ ' + c.getXType() + ' ]': '' ),
452             component: c,
453             uiProvider:Ext.debug.ComponentNodeUI,
454             leaf: !leaf
455         }));
456     },
457
458     parseChildItems: function(n) {
459         var cn = n.attributes.component.items;
460         if (cn) {
461             for (var i = 0;i < cn.length; i++) {
462                 var c = cn.get(i);
463                 if (c.id != this.id && c.id != this.bottomToolbar.id) {
464                     var newNode = this.createNode(n,c);
465                     if (!newNode.leaf) {
466                         this.parseChildItems(newNode)
467                     }
468                 }
469             }
470         }
471     },
472
473     parseRootNode: function() {
474         var n = this.root;
475         var cn = n.attributes.component.items;
476         for (var i = 0,c;c = cn[i];i++) {
477             if (c.id != this.id && c.id != this.bottomToolbar.id) {
478                 if (!c.ownerCt) {
479                     var newNode = this.createNode(n,c);
480                     if (!newNode.leaf) {
481                         this.parseChildItems(newNode);
482                     }
483                 }
484             }
485         }
486     },
487
488     onClick: function(node, e) {
489         var oi = Ext.getCmp('x-debug-objinspector');
490         oi.refreshNodes(node.attributes.component);
491         oi.ownerCt.show();
492     },
493
494     refresh: function() {
495         while (this.root.firstChild) {
496             this.root.removeChild(this.root.firstChild);
497         }
498         this.parseRootNode();
499         var ci = Ext.getCmp('x-debug-compinfo');
500         if (ci) {
501             ci.message('refreshed component tree - '+Ext.ComponentMgr.all.length)
502         }
503     }
504 });
505
506 Ext.debug.ComponentInfoPanel = Ext.extend(Ext.Panel,{
507     id:'x-debug-compinfo',
508     region: 'east',
509     minWidth: 200,
510     split: true,
511     width: 350,
512     border: false,
513     autoScroll: true,
514     layout:'anchor',
515     style:'border-width:0 0 0 1px;',
516
517     initComponent: function() {
518         this.watchBox = new Ext.form.Checkbox({
519             id: 'x-debug-watchcomp',
520             boxLabel: 'Watch ComponentMgr',
521             listeners: {
522                 check: function(cb, val) {
523                     if (val) {
524                         Ext.ComponentMgr.all.on('add', this.onAdd, this);
525                         Ext.ComponentMgr.all.on('remove', this.onRemove, this);
526                     } else {
527                         Ext.ComponentMgr.all.un('add', this.onAdd, this);
528                         Ext.ComponentMgr.all.un('remove', this.onRemove, this);
529                     }
530                 },
531                 scope: this
532             }
533         });
534
535         this.tbar = new Ext.Toolbar([{
536             text: 'Clear',
537             handler: this.clear,
538             scope: this
539         },'->',this.watchBox
540         ]);
541         Ext.debug.ComponentInfoPanel.superclass.initComponent.call(this);
542     },
543
544     onAdd: function(i, o, key) {
545         var markup = ['<div style="padding:5px !important;border-bottom:1px solid #ccc;">',
546                     'Added: '+o.id,
547                     '</div>'].join('');
548         this.insertMarkup(markup);
549     },
550
551     onRemove: function(o, key) {
552         var markup = ['<div style="padding:5px !important;border-bottom:1px solid #ccc;">',
553                     'Removed: '+o.id,
554                     '</div>'].join('');
555         this.insertMarkup(markup);
556     },
557
558     message: function(msg) {
559         var markup = ['<div style="padding:5px !important;border-bottom:1px solid #ccc;">',
560                     msg,
561                     '</div>'].join('');
562         this.insertMarkup(markup);
563     },
564     insertMarkup: function(markup) {
565         this.body.insertHtml('beforeend', markup);
566         this.body.scrollTo('top', 100000);
567     },
568     clear : function(){
569         this.body.update('');
570         this.body.dom.scrollTop = 0;
571     }
572 });
573
574 Ext.debug.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
575     focus: Ext.emptyFn, // prevent odd scrolling behavior
576
577     renderElements : function(n, a, targetNode, bulkRender){
578         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
579
580         var t = n.getOwnerTree();
581         var cols = t.columns;
582         var bw = t.borderWidth;
583         var c = cols[0];
584
585         var buf = [
586              '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',
587                 '<div class="x-tree-col" style="width:',c.width-bw,'px;">',
588                     '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
589                     '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow"/>',
590                     '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on"/>',
591                     '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',
592                     a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',
593                     '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",
594                 "</div>"];
595          for(var i = 1, len = cols.length; i < len; i++){
596              c = cols[i];
597
598              buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',
599                         '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",
600                       "</div>");
601          }
602          buf.push(
603             '<div class="x-clear"></div></div>',
604             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
605             "</li>");
606
607         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
608             this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
609                                 n.nextSibling.ui.getEl(), buf.join(""));
610         }else{
611             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
612         }
613
614         this.elNode = this.wrap.childNodes[0];
615         this.ctNode = this.wrap.childNodes[1];
616         var cs = this.elNode.firstChild.childNodes;
617         this.indentNode = cs[0];
618         this.ecNode = cs[1];
619         this.iconNode = cs[2];
620         this.anchor = cs[3];
621         this.textNode = cs[3].firstChild;
622     }
623 });
624
625 Ext.debug.ObjectInspector = Ext.extend(Ext.tree.TreePanel, {
626     id: 'x-debug-objinspector',
627     enableDD:false ,
628     lines:false,
629     rootVisible:false,
630     animate:false,
631     hlColor:'ffff9c',
632     autoScroll: true,
633     region:'center',
634     border:false,
635     lines:false,
636     borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
637     cls:'x-column-tree',
638
639     initComponent : function(){
640         this.showFunc = false;
641         this.toggleFunc = function() {
642             this.showFunc = !this.showFunc;
643             this.refreshNodes(this.currentObject);
644         }
645         this.bbar = new Ext.Toolbar([{
646             text: 'Show Functions',
647             enableToggle: true,
648             pressed: false,
649             handler: this.toggleFunc,
650             scope: this
651         }]);
652
653         Ext.apply(this,{
654             title: ' ',
655             loader: new Ext.tree.TreeLoader(),
656             columns:[{
657                 header:'Property',
658                 width: 300,
659                 dataIndex:'name'
660             },{
661                 header:'Value',
662                 width: 900,
663                 dataIndex:'value'
664             }]
665         });
666
667         Ext.debug.ObjectInspector.superclass.initComponent.call(this);
668
669         this.root = this.setRootNode(new Ext.tree.TreeNode({
670             text: 'Dummy Node',
671             leaf: false
672         }));
673
674         if (this.currentObject) {
675             this.parseNodes();
676         }
677     },
678
679     refreshNodes: function(newObj) {
680         this.currentObject = newObj;
681         var node = this.root;
682         while(node.firstChild){
683             node.removeChild(node.firstChild);
684         }
685         this.parseNodes();
686     },
687
688     parseNodes: function() {
689         for (var o in this.currentObject) {
690             if (!this.showFunc) {
691                 if (Ext.isFunction(this.currentObject[o])) {
692                     continue;
693                 }
694             }
695             this.createNode(o);
696         }
697     },
698
699     createNode: function(o) {
700         return this.root.appendChild(new Ext.tree.TreeNode({
701             name: o,
702             value: this.currentObject[o],
703             uiProvider:Ext.debug.ColumnNodeUI,
704             iconCls: 'x-debug-node',
705             leaf: true
706         }));
707     },
708
709     onRender : function(){
710         Ext.debug.ObjectInspector.superclass.onRender.apply(this, arguments);
711         this.headers = this.header.createChild({cls:'x-tree-headers'});
712
713         var cols = this.columns, c;
714         var totalWidth = 0;
715
716         for(var i = 0, len = cols.length; i < len; i++){
717              c = cols[i];
718              totalWidth += c.width;
719              this.headers.createChild({
720                  cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),
721                  cn: {
722                      cls:'x-tree-hd-text',
723                      html: c.header
724                  },
725                  style:'width:'+(c.width-this.borderWidth)+'px;'
726              });
727         }
728         this.headers.createChild({cls:'x-clear'});
729         // prevent floats from wrapping when clipped
730         this.headers.setWidth(totalWidth);
731         this.innerCt.setWidth(totalWidth);
732     }
733 });
734
735
736 Ext.debug.StoreInspector = Ext.extend(Ext.tree.TreePanel, {
737     enableDD:false ,
738     lines:false,
739     rootVisible:false,
740     animate:false,
741     hlColor:'ffff9c',
742     autoScroll: true,
743     region:'center',
744     border:false,
745
746     initComponent: function() {
747         this.bbar = new Ext.Toolbar([{
748             text: 'Refresh',
749             handler: this.refresh,
750             scope: this
751         }]);
752         Ext.debug.StoreInspector.superclass.initComponent.call(this);
753
754         this.root = this.setRootNode(new Ext.tree.TreeNode({
755             text: 'Data Stores',
756             leaf: false
757         }));
758         this.on('click', this.onClick, this);
759
760         this.parseStores();
761     },
762
763     parseStores: function() {
764         var cn = Ext.StoreMgr.items;
765         for (var i = 0,c;c = cn[i];i++) {
766             this.root.appendChild({
767                 text: c.storeId + ' - ' + c.totalLength + ' records',
768                 component: c,
769                 leaf: true
770             });
771         }
772     },
773
774     onClick: function(node, e) {
775         var oi = Ext.getCmp('x-debug-objinspector');
776         oi.refreshNodes(node.attributes.component);
777         oi.ownerCt.show();
778     },
779
780     refresh: function() {
781         while (this.root.firstChild) {
782             this.root.removeChild(this.root.firstChild);
783         }
784         this.parseStores();
785     }
786 });
787
788 // highly unusual class declaration
789 Ext.debug.HtmlNode = function(){
790     var html = Ext.util.Format.htmlEncode;
791     var ellipsis = Ext.util.Format.ellipsis;
792     var nonSpace = /^\s*$/;
793
794     var attrs = [
795         {n: 'id', v: 'id'},
796         {n: 'className', v: 'class'},
797         {n: 'name', v: 'name'},
798         {n: 'type', v: 'type'},
799         {n: 'src', v: 'src'},
800         {n: 'href', v: 'href'}
801     ];
802
803     function hasChild(n){
804         for(var i = 0, c; c = n.childNodes[i]; i++){
805             if(c.nodeType == 1){
806                 return true;
807             }
808         }
809         return false;
810     }
811
812     function renderNode(n, leaf){
813         var tag = n.tagName.toLowerCase();
814         var s = '&lt;' + tag;
815         for(var i = 0, len = attrs.length; i < len; i++){
816             var a = attrs[i];
817             var v = n[a.n];
818             if(v && !nonSpace.test(v)){
819                 s += ' ' + a.v + '=&quot;<i>' + html(v) +'</i>&quot;';
820             }
821         }
822         var style = n.style ? n.style.cssText : '';
823         if(style){
824             s += ' style=&quot;<i>' + html(style.toLowerCase()) +'</i>&quot;';
825         }
826         if(leaf && n.childNodes.length > 0){
827             s+='&gt;<em>' + ellipsis(html(String(n.innerHTML)), 35) + '</em>&lt;/'+tag+'&gt;';
828         }else if(leaf){
829             s += ' /&gt;';
830         }else{
831             s += '&gt;';
832         }
833         return s;
834     }
835
836     var HtmlNode = function(n){
837         var leaf = !hasChild(n);
838         this.htmlNode = n;
839         this.tagName = n.tagName.toLowerCase();
840         var attr = {
841             text : renderNode(n, leaf),
842             leaf : leaf,
843             cls: 'x-tree-noicon'
844         };
845         HtmlNode.superclass.constructor.call(this, attr);
846         this.attributes.htmlNode = n; // for searching
847         if(!leaf){
848             this.on('expand', this.onExpand,  this);
849             this.on('collapse', this.onCollapse,  this);
850         }
851     };
852
853
854     Ext.extend(HtmlNode, Ext.tree.AsyncTreeNode, {
855         cls: 'x-tree-noicon',
856         preventHScroll: true,
857         refresh : function(highlight){
858             var leaf = !hasChild(this.htmlNode);
859             this.setText(renderNode(this.htmlNode, leaf));
860             if(highlight){
861                 Ext.fly(this.ui.textNode).highlight();
862             }
863         },
864
865         onExpand : function(){
866             if(!this.closeNode && this.parentNode){
867                 this.closeNode = this.parentNode.insertBefore(new Ext.tree.TreeNode({
868                     text:'&lt;/' + this.tagName + '&gt;',
869                     cls: 'x-tree-noicon'
870                 }), this.nextSibling);
871             }else if(this.closeNode){
872                 this.closeNode.ui.show();
873             }
874         },
875
876         onCollapse : function(){
877             if(this.closeNode){
878                 this.closeNode.ui.hide();
879             }
880         },
881
882         render : function(bulkRender){
883             HtmlNode.superclass.render.call(this, bulkRender);
884         },
885
886         highlightNode : function(){
887             //Ext.fly(this.htmlNode).highlight();
888         },
889
890         highlight : function(){
891             //Ext.fly(this.ui.textNode).highlight();
892         },
893
894         frame : function(){
895             this.htmlNode.style.border = '1px solid #0000ff';
896             //this.highlightNode();
897         },
898
899         unframe : function(){
900             //Ext.fly(this.htmlNode).removeClass('x-debug-frame');
901             this.htmlNode.style.border = '';
902         }
903     });
904
905     return HtmlNode;
906 }();