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