Upgrade to ExtJS 3.1.1 - Released 02/08/2010
[extjs.git] / examples / ux / treegrid / TreeGrid.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 /**
8  * @class Ext.ux.tree.TreeGrid
9  * @extends Ext.tree.TreePanel
10  * 
11  * @xtype treegrid
12  */
13 Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, {
14     rootVisible : false,
15     useArrows : true,
16     lines : false,
17     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
18     cls : 'x-treegrid',
19
20     columnResize : true,
21     enableSort : true,
22     reserveScrollOffset : true,
23     enableHdMenu : true,
24     
25     columnsText : 'Columns',
26
27     initComponent : function() {
28         if(!this.root) {
29             this.root = new Ext.tree.AsyncTreeNode({text: 'Root'});
30         }
31         
32         // initialize the loader
33         var l = this.loader;
34         if(!l){
35             l = new Ext.ux.tree.TreeGridLoader({
36                 dataUrl: this.dataUrl,
37                 requestMethod: this.requestMethod,
38                 store: this.store
39             });
40         }else if(Ext.isObject(l) && !l.load){
41             l = new Ext.ux.tree.TreeGridLoader(l);
42         }
43         else if(l) {
44             l.createNode = function(attr) {
45                 if (!attr.uiProvider) {
46                     attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
47                 }
48                 return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
49             }
50         }
51         this.loader = l;
52                             
53         Ext.ux.tree.TreeGrid.superclass.initComponent.call(this);                    
54         
55         this.initColumns();
56         
57         if(this.enableSort) {
58             this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(this, this.enableSort);
59         }
60         
61         if(this.columnResize){
62             this.colResizer = new Ext.tree.ColumnResizer(this.columnResize);
63             this.colResizer.init(this);
64         }
65         
66         var c = this.columns;
67         if(!this.internalTpl){                                
68             this.internalTpl = new Ext.XTemplate(
69                 '<div class="x-grid3-header">',
70                     '<div class="x-treegrid-header-inner">',
71                         '<div class="x-grid3-header-offset">',
72                             '<table cellspacing="0" cellpadding="0" border="0"><colgroup><tpl for="columns"><col /></tpl></colgroup>',
73                             '<thead><tr class="x-grid3-hd-row">',
74                             '<tpl for="columns">',
75                             '<td class="x-grid3-hd x-grid3-cell x-treegrid-hd" style="text-align: {align};" id="', this.id, '-xlhd-{#}">',
76                                 '<div class="x-grid3-hd-inner x-treegrid-hd-inner" unselectable="on">',
77                                      this.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
78                                      '{header}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
79                                  '</div>',
80                             '</td></tpl>',
81                             '</tr></thead>',
82                         '</div></table>',
83                     '</div></div>',
84                 '</div>',
85                 '<div class="x-treegrid-root-node">',
86                     '<table class="x-treegrid-root-table" cellpadding="0" cellspacing="0" style="table-layout: fixed;"></table>',
87                 '</div>'
88             );
89         }
90         
91         if(!this.colgroupTpl) {
92             this.colgroupTpl = new Ext.XTemplate(
93                 '<colgroup><tpl for="columns"><col style="width: {width}px"/></tpl></colgroup>'
94             );
95         }
96     },
97
98     initColumns : function() {
99         var cs = this.columns,
100             len = cs.length, 
101             columns = [],
102             i, c;
103
104         for(i = 0; i < len; i++){
105             c = cs[i];
106             if(!c.isColumn) {
107                 c.xtype = c.xtype ? (/^tg/.test(c.xtype) ? c.xtype : 'tg' + c.xtype) : 'tgcolumn';
108                 c = Ext.create(c);
109             }
110             c.init(this);
111             columns.push(c);
112             
113             if(this.enableSort !== false && c.sortable !== false) {
114                 c.sortable = true;
115                 this.enableSort = true;
116             }
117         }
118
119         this.columns = columns;
120     },
121
122     onRender : function(){
123         Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);
124
125         this.el.addClass('x-treegrid');
126         
127         this.outerCt = this.body.createChild({
128             cls:'x-tree-root-ct x-treegrid-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')
129         });
130         
131         this.internalTpl.overwrite(this.outerCt, {columns: this.columns});
132         
133         this.mainHd = Ext.get(this.outerCt.dom.firstChild);
134         this.innerHd = Ext.get(this.mainHd.dom.firstChild);
135         this.innerBody = Ext.get(this.outerCt.dom.lastChild);
136         this.innerCt = Ext.get(this.innerBody.dom.firstChild);
137         
138         this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
139         
140         if(this.hideHeaders){
141             this.header.dom.style.display = 'none';
142         }
143         else if(this.enableHdMenu !== false){
144             this.hmenu = new Ext.menu.Menu({id: this.id + '-hctx'});
145             if(this.enableColumnHide !== false){
146                 this.colMenu = new Ext.menu.Menu({id: this.id + '-hcols-menu'});
147                 this.colMenu.on({
148                     scope: this,
149                     beforeshow: this.beforeColMenuShow,
150                     itemclick: this.handleHdMenuClick
151                 });
152                 this.hmenu.add({
153                     itemId:'columns',
154                     hideOnClick: false,
155                     text: this.columnsText,
156                     menu: this.colMenu,
157                     iconCls: 'x-cols-icon'
158                 });
159             }
160             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
161         }
162     },
163
164     setRootNode : function(node){
165         node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI;        
166         node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node);
167         if(this.innerCt) {
168             this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
169         }
170         return node;
171     },
172     
173     clearInnerCt : function(){
174         if(Ext.isIE){
175             var dom = this.innerCt.dom;
176             while(dom.firstChild){
177                 dom.removeChild(dom.firstChild);
178             }
179         }else{
180             Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this);
181         }
182     },
183     
184     initEvents : function() {
185         Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);
186
187         this.mon(this.innerBody, 'scroll', this.syncScroll, this);
188         this.mon(this.innerHd, 'click', this.handleHdDown, this);
189         this.mon(this.mainHd, {
190             scope: this,
191             mouseover: this.handleHdOver,
192             mouseout: this.handleHdOut
193         });
194     },
195     
196     onResize : function(w, h) {
197         Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);
198         
199         var bd = this.innerBody.dom;
200         var hd = this.innerHd.dom;
201
202         if(!bd){
203             return;
204         }
205
206         if(Ext.isNumber(h)){
207             bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px';
208         }
209
210         if(Ext.isNumber(w)){                        
211             var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
212             if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){
213                 this.setScrollOffset(sw);
214             }else{
215                 var me = this;
216                 setTimeout(function(){
217                     me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0);
218                 }, 10);
219             }
220         }
221     },
222
223     updateColumnWidths : function() {
224         var cols = this.columns,
225             colCount = cols.length,
226             groups = this.outerCt.query('colgroup'),
227             groupCount = groups.length,
228             c, g, i, j;
229
230         for(i = 0; i<colCount; i++) {
231             c = cols[i];
232             for(j = 0; j<groupCount; j++) {
233                 g = groups[j];
234                 g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
235             }
236         }
237         
238         for(i = 0, groups = this.innerHd.query('td'), len = groups.length; i<len; i++) {
239             c = Ext.fly(groups[i]);
240             if(cols[i] && cols[i].hidden) {
241                 c.addClass('x-treegrid-hd-hidden');
242             }
243             else {
244                 c.removeClass('x-treegrid-hd-hidden');
245             }
246         }
247
248         var tcw = this.getTotalColumnWidth();                        
249         Ext.fly(this.innerHd.dom.firstChild).setWidth(tcw + (this.scrollOffset || 0));
250         this.outerCt.select('table').setWidth(tcw);
251         this.syncHeaderScroll();    
252     },
253                     
254     getVisibleColumns : function() {
255         var columns = [],
256             cs = this.columns,
257             len = cs.length,
258             i;
259             
260         for(i = 0; i<len; i++) {
261             if(!cs[i].hidden) {
262                 columns.push(cs[i]);
263             }
264         }        
265         return columns;
266     },
267
268     getTotalColumnWidth : function() {
269         var total = 0;
270         for(var i = 0, cs = this.getVisibleColumns(), len = cs.length; i<len; i++) {
271             total += cs[i].width;
272         }
273         return total;
274     },
275
276     setScrollOffset : function(scrollOffset) {
277         this.scrollOffset = scrollOffset;                        
278         this.updateColumnWidths();
279     },
280
281     // private
282     handleHdDown : function(e, t){
283         var hd = e.getTarget('.x-treegrid-hd');
284
285         if(hd && Ext.fly(t).hasClass('x-grid3-hd-btn')){
286             var ms = this.hmenu.items,
287                 cs = this.columns,
288                 index = this.findHeaderIndex(hd),
289                 c = cs[index],
290                 sort = c.sortable;
291                 
292             e.stopEvent();
293             Ext.fly(hd).addClass('x-grid3-hd-menu-open');
294             this.hdCtxIndex = index;
295             
296             this.fireEvent('headerbuttonclick', ms, c, hd, index);
297             
298             this.hmenu.on('hide', function(){
299                 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
300             }, this, {single:true});
301             
302             this.hmenu.show(t, 'tl-bl?');
303         }
304         else if(hd) {
305             var index = this.findHeaderIndex(hd);
306             this.fireEvent('headerclick', this.columns[index], hd, index);
307         }
308     },
309
310     // private
311     handleHdOver : function(e, t){                    
312         var hd = e.getTarget('.x-treegrid-hd');                        
313         if(hd && !this.headersDisabled){
314             index = this.findHeaderIndex(hd);
315             this.activeHdRef = t;
316             this.activeHdIndex = index;
317             var el = Ext.get(hd);
318             this.activeHdRegion = el.getRegion();
319             el.addClass('x-grid3-hd-over');
320             this.activeHdBtn = el.child('.x-grid3-hd-btn');
321             if(this.activeHdBtn){
322                 this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
323             }
324         }
325     },
326     
327     // private
328     handleHdOut : function(e, t){
329         var hd = e.getTarget('.x-treegrid-hd');
330         if(hd && (!Ext.isIE || !e.within(hd, true))){
331             this.activeHdRef = null;
332             Ext.fly(hd).removeClass('x-grid3-hd-over');
333             hd.style.cursor = '';
334         }
335     },
336                     
337     findHeaderIndex : function(hd){
338         hd = hd.dom || hd;
339         var cs = hd.parentNode.childNodes;
340         for(var i = 0, c; c = cs[i]; i++){
341             if(c == hd){
342                 return i;
343             }
344         }
345         return -1;
346     },
347     
348     // private
349     beforeColMenuShow : function(){
350         var cols = this.columns,  
351             colCount = cols.length,
352             i, c;                        
353         this.colMenu.removeAll();                    
354         for(i = 1; i < colCount; i++){
355             c = cols[i];
356             if(c.hideable !== false){
357                 this.colMenu.add(new Ext.menu.CheckItem({
358                     itemId: 'col-' + i,
359                     text: c.header,
360                     checked: !c.hidden,
361                     hideOnClick:false,
362                     disabled: c.hideable === false
363                 }));
364             }
365         }
366     },
367                     
368     // private
369     handleHdMenuClick : function(item){
370         var index = this.hdCtxIndex,
371             id = item.getItemId();
372         
373         if(this.fireEvent('headermenuclick', this.columns[index], id, index) !== false) {
374             index = id.substr(4);
375             if(index > 0 && this.columns[index]) {
376                 this.setColumnVisible(index, !item.checked);
377             }     
378         }
379         
380         return true;
381     },
382     
383     setColumnVisible : function(index, visible) {
384         this.columns[index].hidden = !visible;        
385         this.updateColumnWidths();
386     },
387
388     /**
389      * Scrolls the grid to the top
390      */
391     scrollToTop : function(){
392         this.innerBody.dom.scrollTop = 0;
393         this.innerBody.dom.scrollLeft = 0;
394     },
395
396     // private
397     syncScroll : function(){
398         this.syncHeaderScroll();
399         var mb = this.innerBody.dom;
400         this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
401     },
402
403     // private
404     syncHeaderScroll : function(){
405         var mb = this.innerBody.dom;
406         this.innerHd.dom.scrollLeft = mb.scrollLeft;
407         this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
408     },
409     
410     registerNode : function(n) {
411         Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
412         if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
413             n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
414         }
415     }
416 });
417
418 Ext.reg('treegrid', Ext.ux.tree.TreeGrid);