Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / examples / docs / source / ColumnHeaderGroup.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.3.1
11  * Copyright(c) 2006-2010 Sencha Inc.
12  * licensing@sencha.com
13  * http://www.sencha.com/license
14  */
15 Ext.ns('Ext.ux.grid');
16
17 Ext.ux.grid.ColumnHeaderGroup = Ext.extend(Ext.util.Observable, {
18
19     constructor: function(config){
20         this.config = config;
21     },
22
23     init: function(grid){
24         Ext.applyIf(grid.colModel, this.config);
25         Ext.apply(grid.getView(), this.viewConfig);
26     },
27
28     viewConfig: {
29         initTemplates: function(){
30             this.constructor.prototype.initTemplates.apply(this, arguments);
31             var ts = this.templates || {};
32             if(!ts.gcell){
33                 ts.gcell = new Ext.XTemplate('<td class="x-grid3-hd x-grid3-gcell x-grid3-td-{id} ux-grid-hd-group-row-{row} {cls}" style="{style}">', '<div {tooltip} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '', '{value}</div></td>');
34             }
35             this.templates = ts;
36             this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", "");
37         },
38
39         renderHeaders: function(){
40             var ts = this.templates, headers = [], cm = this.cm, rows = cm.rows, tstyle = 'width:' + this.getTotalWidth() + ';';
41
42             for(var row = 0, rlen = rows.length; row < rlen; row++){
43                 var r = rows[row], cells = [];
44                 for(var i = 0, gcol = 0, len = r.length; i < len; i++){
45                     var group = r[i];
46                     group.colspan = group.colspan || 1;
47                     var id = this.getColumnId(group.dataIndex ? cm.findColumnIndex(group.dataIndex) : gcol), gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol);
48                     cells[i] = ts.gcell.apply({
49                         cls: 'ux-grid-hd-group-cell',
50                         id: id,
51                         row: row,
52                         style: 'width:' + gs.width + ';' + (gs.hidden ? 'display:none;' : '') + (group.align ? 'text-align:' + group.align + ';' : ''),
53                         tooltip: group.tooltip ? (Ext.QuickTips.isEnabled() ? 'ext:qtip' : 'title') + '="' + group.tooltip + '"' : '',
54                         istyle: group.align == 'right' ? 'padding-right:16px' : '',
55                         btn: this.grid.enableHdMenu && group.header,
56                         value: group.header || '&nbsp;'
57                     });
58                     gcol += group.colspan;
59                 }
60                 headers[row] = ts.header.apply({
61                     tstyle: tstyle,
62                     cells: cells.join('')
63                 });
64             }
65             headers.push(this.constructor.prototype.renderHeaders.apply(this, arguments));
66             return headers.join('');
67         },
68
69         onColumnWidthUpdated: function(){
70             this.constructor.prototype.onColumnWidthUpdated.apply(this, arguments);
71             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);
72         },
73
74         onAllColumnWidthsUpdated: function(){
75             this.constructor.prototype.onAllColumnWidthsUpdated.apply(this, arguments);
76             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);
77         },
78
79         onColumnHiddenUpdated: function(){
80             this.constructor.prototype.onColumnHiddenUpdated.apply(this, arguments);
81             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);
82         },
83
84         getHeaderCell: function(index){
85             return this.mainHd.query(this.cellSelector)[index];
86         },
87
88         findHeaderCell: function(el){
89             return el ? this.fly(el).findParent('td.x-grid3-hd', this.cellSelectorDepth) : false;
90         },
91
92         findHeaderIndex: function(el){
93             var cell = this.findHeaderCell(el);
94             return cell ? this.getCellIndex(cell) : false;
95         },
96
97         updateSortIcon: function(col, dir){
98             var sc = this.sortClasses, hds = this.mainHd.select(this.cellSelector).removeClass(sc);
99             hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
100         },
101
102         handleHdDown: function(e, t){
103             var el = Ext.get(t);
104             if(el.hasClass('x-grid3-hd-btn')){
105                 e.stopEvent();
106                 var hd = this.findHeaderCell(t);
107                 Ext.fly(hd).addClass('x-grid3-hd-menu-open');
108                 var index = this.getCellIndex(hd);
109                 this.hdCtxIndex = index;
110                 var ms = this.hmenu.items, cm = this.cm;
111                 ms.get('asc').setDisabled(!cm.isSortable(index));
112                 ms.get('desc').setDisabled(!cm.isSortable(index));
113                 this.hmenu.on('hide', function(){
114                     Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
115                 }, this, {
116                     single: true
117                 });
118                 this.hmenu.show(t, 'tl-bl?');
119             }else if(el.hasClass('ux-grid-hd-group-cell') || Ext.fly(t).up('.ux-grid-hd-group-cell')){
120                 e.stopEvent();
121             }
122         },
123
124         handleHdMove: function(e, t){
125             var hd = this.findHeaderCell(this.activeHdRef);
126             if(hd && !this.headersDisabled && !Ext.fly(hd).hasClass('ux-grid-hd-group-cell')){
127                 var hw = this.splitHandleWidth || 5, r = this.activeHdRegion, x = e.getPageX(), ss = hd.style, cur = '';
128                 if(this.grid.enableColumnResize !== false){
129                     if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex - 1)){
130                         cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize
131                                                                                                 // not
132                                                                                                 // always
133                                                                                                 // supported
134                     }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){
135                         cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
136                     }
137                 }
138                 ss.cursor = cur;
139             }
140         },
141
142         handleHdOver: function(e, t){
143             var hd = this.findHeaderCell(t);
144             if(hd && !this.headersDisabled){
145                 this.activeHdRef = t;
146                 this.activeHdIndex = this.getCellIndex(hd);
147                 var fly = this.fly(hd);
148                 this.activeHdRegion = fly.getRegion();
149                 if(!(this.cm.isMenuDisabled(this.activeHdIndex) || fly.hasClass('ux-grid-hd-group-cell'))){
150                     fly.addClass('x-grid3-hd-over');
151                     this.activeHdBtn = fly.child('.x-grid3-hd-btn');
152                     if(this.activeHdBtn){
153                         this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight - 1) + 'px';
154                     }
155                 }
156             }
157         },
158
159         handleHdOut: function(e, t){
160             var hd = this.findHeaderCell(t);
161             if(hd && (!Ext.isIE || !e.within(hd, true))){
162                 this.activeHdRef = null;
163                 this.fly(hd).removeClass('x-grid3-hd-over');
164                 hd.style.cursor = '';
165             }
166         },
167
168         handleHdMenuClick: function(item){
169             var index = this.hdCtxIndex, cm = this.cm, ds = this.ds, id = item.getItemId();
170             switch(id){
171                 case 'asc':
172                     ds.sort(cm.getDataIndex(index), 'ASC');
173                     break;
174                 case 'desc':
175                     ds.sort(cm.getDataIndex(index), 'DESC');
176                     break;
177                 default:
178                     if(id.substr(0, 6) == 'group-'){
179                         var i = id.split('-'), row = parseInt(i[1], 10), col = parseInt(i[2], 10), r = this.cm.rows[row], group, gcol = 0;
180                         for(var i = 0, len = r.length; i < len; i++){
181                             group = r[i];
182                             if(col >= gcol && col < gcol + group.colspan){
183                                 break;
184                             }
185                             gcol += group.colspan;
186                         }
187                         if(item.checked){
188                             var max = cm.getColumnsBy(this.isHideableColumn, this).length;
189                             for(var i = gcol, len = gcol + group.colspan; i < len; i++){
190                                 if(!cm.isHidden(i)){
191                                     max--;
192                                 }
193                             }
194                             if(max < 1){
195                                 this.onDenyColumnHide();
196                                 return false;
197                             }
198                         }
199                         for(var i = gcol, len = gcol + group.colspan; i < len; i++){
200                             if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){
201                                 cm.setHidden(i, item.checked);
202                             }
203                         }
204                     }else if(id.substr(0, 4) == 'col-'){
205                         index = cm.getIndexById(id.substr(4));
206                         if(index != -1){
207                             if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
208                                 this.onDenyColumnHide();
209                                 return false;
210                             }
211                             cm.setHidden(index, item.checked);
212                         }
213                     }
214                     if(id.substr(0, 6) == 'group-' || id.substr(0, 4) == 'col-'){
215                         item.checked = !item.checked;
216                         if(item.menu){
217                             var updateChildren = function(menu){
218                                 menu.items.each(function(childItem){
219                                     if(!childItem.disabled){
220                                         childItem.setChecked(item.checked, false);
221                                         if(childItem.menu){
222                                             updateChildren(childItem.menu);
223                                         }
224                                     }
225                                 });
226                             }
227                             updateChildren(item.menu);
228                         }
229                         var parentMenu = item, parentItem;
230                         while(parentMenu = parentMenu.parentMenu){
231                             if(!parentMenu.parentMenu || !(parentItem = parentMenu.parentMenu.items.get(parentMenu.getItemId())) || !parentItem.setChecked){
232                                 break;
233                             }
234                             var checked = parentMenu.items.findIndexBy(function(m){
235                                 return m.checked;
236                             }) >= 0;
237                             parentItem.setChecked(checked, true);
238                         }
239                         item.checked = !item.checked;
240                     }
241             }
242             return true;
243         },
244
245         beforeColMenuShow: function(){
246             var cm = this.cm, rows = this.cm.rows;
247             this.colMenu.removeAll();
248             for(var col = 0, clen = cm.getColumnCount(); col < clen; col++){
249                 var menu = this.colMenu, title = cm.getColumnHeader(col), text = [];
250                 if(cm.config[col].fixed !== true && cm.config[col].hideable !== false){
251                     for(var row = 0, rlen = rows.length; row < rlen; row++){
252                         var r = rows[row], group, gcol = 0;
253                         for(var i = 0, len = r.length; i < len; i++){
254                             group = r[i];
255                             if(col >= gcol && col < gcol + group.colspan){
256                                 break;
257                             }
258                             gcol += group.colspan;
259                         }
260                         if(group && group.header){
261                             if(cm.hierarchicalColMenu){
262                                 var gid = 'group-' + row + '-' + gcol,
263                                     item = menu.items ? menu.getComponent(gid) : null,
264                                     submenu = item ? item.menu : null;
265                                 if(!submenu){
266                                     submenu = new Ext.menu.Menu({
267                                         itemId: gid
268                                     });
269                                     submenu.on("itemclick", this.handleHdMenuClick, this);
270                                     var checked = false, disabled = true;
271                                     for(var c = gcol, lc = gcol + group.colspan; c < lc; c++){
272                                         if(!cm.isHidden(c)){
273                                             checked = true;
274                                         }
275                                         if(cm.config[c].hideable !== false){
276                                             disabled = false;
277                                         }
278                                     }
279                                     menu.add({
280                                         itemId: gid,
281                                         text: group.header,
282                                         menu: submenu,
283                                         hideOnClick: false,
284                                         checked: checked,
285                                         disabled: disabled
286                                     });
287                                 }
288                                 menu = submenu;
289                             }else{
290                                 text.push(group.header);
291                             }
292                         }
293                     }
294                     text.push(title);
295                     menu.add(new Ext.menu.CheckItem({
296                         itemId: "col-" + cm.getColumnId(col),
297                         text: text.join(' '),
298                         checked: !cm.isHidden(col),
299                         hideOnClick: false,
300                         disabled: cm.config[col].hideable === false
301                     }));
302                 }
303             }
304         },
305
306         afterRenderUI: function(){
307             this.constructor.prototype.afterRenderUI.apply(this, arguments);
308             Ext.apply(this.columnDrop, Ext.ux.grid.ColumnHeaderGroup.prototype.columnDropConfig);
309             Ext.apply(this.splitZone, Ext.ux.grid.ColumnHeaderGroup.prototype.splitZoneConfig);
310         }
311     },
312
313     splitZoneConfig: {
314         allowHeaderDrag: function(e){
315             return !e.getTarget(null, null, true).hasClass('ux-grid-hd-group-cell');
316         }
317     },
318
319     columnDropConfig: {
320         getTargetFromEvent: function(e){
321             var t = Ext.lib.Event.getTarget(e);
322             return this.view.findHeaderCell(t);
323         },
324
325         positionIndicator: function(h, n, e){
326             var data = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e);
327             if(data === false){
328                 return false;
329             }
330             var px = data.px + this.proxyOffsets[0];
331             this.proxyTop.setLeftTop(px, data.r.top + this.proxyOffsets[1]);
332             this.proxyTop.show();
333             this.proxyBottom.setLeftTop(px, data.r.bottom);
334             this.proxyBottom.show();
335             return data.pt;
336         },
337
338         onNodeDrop: function(n, dd, e, data){
339             var h = data.header;
340             if(h != n){
341                 var d = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e);
342                 if(d === false){
343                     return false;
344                 }
345                 var cm = this.grid.colModel, right = d.oldIndex < d.newIndex, rows = cm.rows;
346                 for(var row = d.row, rlen = rows.length; row < rlen; row++){
347                     var r = rows[row], len = r.length, fromIx = 0, span = 1, toIx = len;
348                     for(var i = 0, gcol = 0; i < len; i++){
349                         var group = r[i];
350                         if(d.oldIndex >= gcol && d.oldIndex < gcol + group.colspan){
351                             fromIx = i;
352                         }
353                         if(d.oldIndex + d.colspan - 1 >= gcol && d.oldIndex + d.colspan - 1 < gcol + group.colspan){
354                             span = i - fromIx + 1;
355                         }
356                         if(d.newIndex >= gcol && d.newIndex < gcol + group.colspan){
357                             toIx = i;
358                         }
359                         gcol += group.colspan;
360                     }
361                     var groups = r.splice(fromIx, span);
362                     rows[row] = r.splice(0, toIx - (right ? span : 0)).concat(groups).concat(r);
363                 }
364                 for(var c = 0; c < d.colspan; c++){
365                     var oldIx = d.oldIndex + (right ? 0 : c), newIx = d.newIndex + (right ? -1 : c);
366                     cm.moveColumn(oldIx, newIx);
367                     this.grid.fireEvent("columnmove", oldIx, newIx);
368                 }
369                 return true;
370             }
371             return false;
372         }
373     },
374
375     getGroupStyle: function(group, gcol){
376         var width = 0, hidden = true;
377         for(var i = gcol, len = gcol + group.colspan; i < len; i++){
378             if(!this.cm.isHidden(i)){
379                 var cw = this.cm.getColumnWidth(i);
380                 if(typeof cw == 'number'){
381                     width += cw;
382                 }
383                 hidden = false;
384             }
385         }
386         return {
387             width: (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? width : Math.max(width - this.borderWidth, 0)) + 'px',
388             hidden: hidden
389         };
390     },
391
392     updateGroupStyles: function(col){
393         var tables = this.mainHd.query('.x-grid3-header-offset > table'), tw = this.getTotalWidth(), rows = this.cm.rows;
394         for(var row = 0; row < tables.length; row++){
395             tables[row].style.width = tw;
396             if(row < rows.length){
397                 var cells = tables[row].firstChild.firstChild.childNodes;
398                 for(var i = 0, gcol = 0; i < cells.length; i++){
399                     var group = rows[row][i];
400                     if((typeof col != 'number') || (col >= gcol && col < gcol + group.colspan)){
401                         var gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol);
402                         cells[i].style.width = gs.width;
403                         cells[i].style.display = gs.hidden ? 'none' : '';
404                     }
405                     gcol += group.colspan;
406                 }
407             }
408         }
409     },
410
411     getGroupRowIndex: function(el){
412         if(el){
413             var m = el.className.match(this.hrowRe);
414             if(m && m[1]){
415                 return parseInt(m[1], 10);
416             }
417         }
418         return this.cm.rows.length;
419     },
420
421     getGroupSpan: function(row, col){
422         if(row < 0){
423             return {
424                 col: 0,
425                 colspan: this.cm.getColumnCount()
426             };
427         }
428         var r = this.cm.rows[row];
429         if(r){
430             for(var i = 0, gcol = 0, len = r.length; i < len; i++){
431                 var group = r[i];
432                 if(col >= gcol && col < gcol + group.colspan){
433                     return {
434                         col: gcol,
435                         colspan: group.colspan
436                     };
437                 }
438                 gcol += group.colspan;
439             }
440             return {
441                 col: gcol,
442                 colspan: 0
443             };
444         }
445         return {
446             col: col,
447             colspan: 1
448         };
449     },
450
451     getDragDropData: function(h, n, e){
452         if(h.parentNode != n.parentNode){
453             return false;
454         }
455         var cm = this.grid.colModel, x = Ext.lib.Event.getPageX(e), r = Ext.lib.Dom.getRegion(n.firstChild), px, pt;
456         if((r.right - x) <= (r.right - r.left) / 2){
457             px = r.right + this.view.borderWidth;
458             pt = "after";
459         }else{
460             px = r.left;
461             pt = "before";
462         }
463         var oldIndex = this.view.getCellIndex(h), newIndex = this.view.getCellIndex(n);
464         if(cm.isFixed(newIndex)){
465             return false;
466         }
467         var row = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupRowIndex.call(this.view, h),
468             oldGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, oldIndex),
469             newGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, newIndex),
470             oldIndex = oldGroup.col;
471             newIndex = newGroup.col + (pt == "after" ? newGroup.colspan : 0);
472         if(newIndex >= oldGroup.col && newIndex <= oldGroup.col + oldGroup.colspan){
473             return false;
474         }
475         var parentGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row - 1, oldIndex);
476         if(newIndex < parentGroup.col || newIndex > parentGroup.col + parentGroup.colspan){
477             return false;
478         }
479         return {
480             r: r,
481             px: px,
482             pt: pt,
483             row: row,
484             oldIndex: oldIndex,
485             newIndex: newIndex,
486             colspan: oldGroup.colspan
487         };
488     }
489 });</pre>    
490 </body>
491 </html>