Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[extjs.git] / examples / ux / treegrid / TreeGrid.js
1 /*!
2  * Ext JS Library 3.1.0
3  * Copyright(c) 2006-2009 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     initEvents : function() {
174         Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);
175
176         this.mon(this.innerBody, 'scroll', this.syncScroll, this);
177         this.mon(this.innerHd, 'click', this.handleHdDown, this);
178         this.mon(this.mainHd, {
179             scope: this,
180             mouseover: this.handleHdOver,
181             mouseout: this.handleHdOut
182         });
183     },
184     
185     onResize : function(w, h) {
186         Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);
187         
188         var bd = this.innerBody.dom;
189         var hd = this.innerHd.dom;
190
191         if(!bd){
192             return;
193         }
194
195         if(Ext.isNumber(h)){
196             bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px';
197         }
198
199         if(Ext.isNumber(w)){                        
200             var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
201             if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){
202                 this.setScrollOffset(sw);
203             }else{
204                 var me = this;
205                 setTimeout(function(){
206                     me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0);
207                 }, 10);
208             }
209         }
210     },
211
212     updateColumnWidths : function() {
213         var cols = this.columns,
214             colCount = cols.length,
215             groups = this.outerCt.query('colgroup'),
216             groupCount = groups.length,
217             c, g, i, j;
218
219         for(i = 0; i<colCount; i++) {
220             c = cols[i];
221             for(j = 0; j<groupCount; j++) {
222                 g = groups[j];
223                 g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
224             }
225         }
226         
227         for(i = 0, groups = this.innerHd.query('td'), len = groups.length; i<len; i++) {
228             c = Ext.fly(groups[i]);
229             if(cols[i] && cols[i].hidden) {
230                 c.addClass('x-treegrid-hd-hidden');
231             }
232             else {
233                 c.removeClass('x-treegrid-hd-hidden');
234             }
235         }
236
237         var tcw = this.getTotalColumnWidth();                        
238         Ext.fly(this.innerHd.dom.firstChild).setWidth(tcw + (this.scrollOffset || 0));
239         this.outerCt.select('table').setWidth(tcw);
240         this.syncHeaderScroll();    
241     },
242                     
243     getVisibleColumns : function() {
244         var columns = [],
245             cs = this.columns,
246             len = cs.length,
247             i;
248             
249         for(i = 0; i<len; i++) {
250             if(!cs[i].hidden) {
251                 columns.push(cs[i]);
252             }
253         }        
254         return columns;
255     },
256
257     getTotalColumnWidth : function() {
258         var total = 0;
259         for(var i = 0, cs = this.getVisibleColumns(), len = cs.length; i<len; i++) {
260             total += cs[i].width;
261         }
262         return total;
263     },
264
265     setScrollOffset : function(scrollOffset) {
266         this.scrollOffset = scrollOffset;                        
267         this.updateColumnWidths();
268     },
269
270     // private
271     handleHdDown : function(e, t){
272         var hd = e.getTarget('.x-treegrid-hd');
273
274         if(hd && Ext.fly(t).hasClass('x-grid3-hd-btn')){
275             var ms = this.hmenu.items,
276                 cs = this.columns,
277                 index = this.findHeaderIndex(hd),
278                 c = cs[index],
279                 sort = c.sortable;
280                 
281             e.stopEvent();
282             Ext.fly(hd).addClass('x-grid3-hd-menu-open');
283             this.hdCtxIndex = index;
284             
285             this.fireEvent('headerbuttonclick', ms, c, hd, index);
286             
287             this.hmenu.on('hide', function(){
288                 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
289             }, this, {single:true});
290             
291             this.hmenu.show(t, 'tl-bl?');
292         }
293         else if(hd) {
294             var index = this.findHeaderIndex(hd);
295             this.fireEvent('headerclick', this.columns[index], hd, index);
296         }
297     },
298
299     // private
300     handleHdOver : function(e, t){                    
301         var hd = e.getTarget('.x-treegrid-hd');                        
302         if(hd && !this.headersDisabled){
303             index = this.findHeaderIndex(hd);
304             this.activeHdRef = t;
305             this.activeHdIndex = index;
306             var el = Ext.get(hd);
307             this.activeHdRegion = el.getRegion();
308             el.addClass('x-grid3-hd-over');
309             this.activeHdBtn = el.child('.x-grid3-hd-btn');
310             if(this.activeHdBtn){
311                 this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
312             }
313         }
314     },
315     
316     // private
317     handleHdOut : function(e, t){
318         var hd = e.getTarget('.x-treegrid-hd');
319         if(hd && (!Ext.isIE || !e.within(hd, true))){
320             this.activeHdRef = null;
321             Ext.fly(hd).removeClass('x-grid3-hd-over');
322             hd.style.cursor = '';
323         }
324     },
325                     
326     findHeaderIndex : function(hd){
327         hd = hd.dom || hd;
328         var cs = hd.parentNode.childNodes;
329         for(var i = 0, c; c = cs[i]; i++){
330             if(c == hd){
331                 return i;
332             }
333         }
334         return -1;
335     },
336     
337     // private
338     beforeColMenuShow : function(){
339         var cols = this.columns,  
340             colCount = cols.length,
341             i, c;                        
342         this.colMenu.removeAll();                    
343         for(i = 1; i < colCount; i++){
344             c = cols[i];
345             if(c.hideable !== false){
346                 this.colMenu.add(new Ext.menu.CheckItem({
347                     itemId: 'col-' + i,
348                     text: c.header,
349                     checked: !c.hidden,
350                     hideOnClick:false,
351                     disabled: c.hideable === false
352                 }));
353             }
354         }
355     },
356                     
357     // private
358     handleHdMenuClick : function(item){
359         var index = this.hdCtxIndex,
360             id = item.getItemId();
361         
362         if(this.fireEvent('headermenuclick', this.columns[index], id, index) !== false) {
363             index = id.substr(4);
364             if(index > 0 && this.columns[index]) {
365                 this.setColumnVisible(index, !item.checked);
366             }     
367         }
368         
369         return true;
370     },
371     
372     setColumnVisible : function(index, visible) {
373         this.columns[index].hidden = !visible;        
374         this.updateColumnWidths();
375     },
376
377     /**
378      * Scrolls the grid to the top
379      */
380     scrollToTop : function(){
381         this.innerBody.dom.scrollTop = 0;
382         this.innerBody.dom.scrollLeft = 0;
383     },
384
385     // private
386     syncScroll : function(){
387         this.syncHeaderScroll();
388         var mb = this.innerBody.dom;
389         this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
390     },
391
392     // private
393     syncHeaderScroll : function(){
394         var mb = this.innerBody.dom;
395         this.innerHd.dom.scrollLeft = mb.scrollLeft;
396         this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
397     },
398     
399     registerNode : function(n) {
400         Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
401         if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
402             n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
403         }
404     }
405 });
406
407 Ext.reg('treegrid', Ext.ux.tree.TreeGrid);