Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / docs / source / Grouping.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-grid-feature-Grouping'>/**
19 </span> * @class Ext.grid.feature.Grouping
20  * @extends Ext.grid.feature.Feature
21  * 
22  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
23  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
24  * underneath. The groups can also be expanded and collapsed.
25  * 
26  * ## Extra Events
27  * This feature adds several extra events that will be fired on the grid to interact with the groups:
28  *
29  *  - {@link #groupclick}
30  *  - {@link #groupdblclick}
31  *  - {@link #groupcontextmenu}
32  *  - {@link #groupexpand}
33  *  - {@link #groupcollapse}
34  * 
35  * ## Menu Augmentation
36  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
37  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
38  * by thew user is {@link #enableNoGroups}.
39  * 
40  * ## Controlling Group Text
41  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
42  * the default display.
43  * 
44  * ## Example Usage
45  * 
46  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
47  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
48  *         startCollapsed: true // start all groups collapsed
49  *     });
50  * 
51  * @ftype grouping
52  * @author Nicolas Ferrero
53  */
54 Ext.define('Ext.grid.feature.Grouping', {
55     extend: 'Ext.grid.feature.Feature',
56     alias: 'feature.grouping',
57
58     eventPrefix: 'group',
59     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
60
61     constructor: function() {
62         this.collapsedState = {};
63         this.callParent(arguments);
64     },
65     
66 <span id='Ext-grid-feature-Grouping-event-groupclick'>    /**
67 </span>     * @event groupclick
68      * @param {Ext.view.Table} view
69      * @param {HTMLElement} node
70      * @param {String} group The name of the group
71      * @param {Ext.EventObject} e
72      */
73
74 <span id='Ext-grid-feature-Grouping-event-groupdblclick'>    /**
75 </span>     * @event groupdblclick
76      * @param {Ext.view.Table} view
77      * @param {HTMLElement} node
78      * @param {String} group The name of the group
79      * @param {Ext.EventObject} e
80      */
81
82 <span id='Ext-grid-feature-Grouping-event-groupcontextmenu'>    /**
83 </span>     * @event groupcontextmenu
84      * @param {Ext.view.Table} view
85      * @param {HTMLElement} node
86      * @param {String} group The name of the group
87      * @param {Ext.EventObject} e
88      */
89
90 <span id='Ext-grid-feature-Grouping-event-groupcollapse'>    /**
91 </span>     * @event groupcollapse
92      * @param {Ext.view.Table} view
93      * @param {HTMLElement} node
94      * @param {String} group The name of the group
95      * @param {Ext.EventObject} e
96      */
97
98 <span id='Ext-grid-feature-Grouping-event-groupexpand'>    /**
99 </span>     * @event groupexpand
100      * @param {Ext.view.Table} view
101      * @param {HTMLElement} node
102      * @param {String} group The name of the group
103      * @param {Ext.EventObject} e
104      */
105
106 <span id='Ext-grid-feature-Grouping-cfg-groupHeaderTpl'>    /**
107 </span>     * @cfg {String} groupHeaderTpl
108      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
109      * Defaults to 'Group: {name}'
110      */
111     groupHeaderTpl: 'Group: {name}',
112
113 <span id='Ext-grid-feature-Grouping-cfg-depthToIndent'>    /**
114 </span>     * @cfg {Number} depthToIndent
115      * Number of pixels to indent per grouping level
116      */
117     depthToIndent: 17,
118
119     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
120     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
121
122 <span id='Ext-grid-feature-Grouping-cfg-groupByText'>    /**
123 </span>     * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header
124      * (defaults to 'Group By This Field').
125      */
126     groupByText : 'Group By This Field',
127 <span id='Ext-grid-feature-Grouping-cfg-showGroupsText'>    /**
128 </span>     * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
129      * (defaults to 'Show in Groups').
130      */
131     showGroupsText : 'Show in Groups',
132
133 <span id='Ext-grid-feature-Grouping-cfg-hideGroupedHeader'>    /**
134 </span>     * @cfg {Boolean} hideGroupedHeader&lt;tt&gt;true&lt;/tt&gt; to hide the header that is currently grouped (defaults to &lt;tt&gt;false&lt;/tt&gt;)
135      */
136     hideGroupedHeader : false,
137
138 <span id='Ext-grid-feature-Grouping-cfg-startCollapsed'>    /**
139 </span>     * @cfg {Boolean} startCollapsed &lt;tt&gt;true&lt;/tt&gt; to start all groups collapsed (defaults to &lt;tt&gt;false&lt;/tt&gt;)
140      */
141     startCollapsed : false,
142
143 <span id='Ext-grid-feature-Grouping-cfg-enableGroupingMenu'>    /**
144 </span>     * @cfg {Boolean} enableGroupingMenu &lt;tt&gt;true&lt;/tt&gt; to enable the grouping control in the header menu (defaults to &lt;tt&gt;true&lt;/tt&gt;)
145      */
146     enableGroupingMenu : true,
147
148 <span id='Ext-grid-feature-Grouping-cfg-enableNoGroups'>    /**
149 </span>     * @cfg {Boolean} enableNoGroups &lt;tt&gt;true&lt;/tt&gt; to allow the user to turn off grouping (defaults to &lt;tt&gt;true&lt;/tt&gt;)
150      */
151     enableNoGroups : true,
152     
153     enable: function() {
154         var me    = this,
155             view  = me.view,
156             store = view.store,
157             groupToggleMenuItem;
158             
159         if (me.lastGroupIndex) {
160             store.group(me.lastGroupIndex);
161         }
162         me.callParent();
163         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
164         groupToggleMenuItem.setChecked(true, true);
165         view.refresh();
166     },
167
168     disable: function() {
169         var me    = this,
170             view  = me.view,
171             store = view.store,
172             groupToggleMenuItem,
173             lastGroup;
174             
175         lastGroup = store.groupers.first();
176         if (lastGroup) {
177             me.lastGroupIndex = lastGroup.property;
178             store.groupers.clear();
179         }
180         
181         me.callParent();
182         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
183         groupToggleMenuItem.setChecked(true, true);
184         groupToggleMenuItem.setChecked(false, true);
185         view.refresh();
186     },
187
188     getFeatureTpl: function(values, parent, x, xcount) {
189         var me = this;
190         
191         return [
192             '&lt;tpl if=&quot;typeof rows !== \'undefined\'&quot;&gt;',
193                 // group row tpl
194                 '&lt;tr class=&quot;' + Ext.baseCSSPrefix + 'grid-group-hd ' + (me.startCollapsed ? me.hdCollapsedCls : '') + ' {hdCollapsedCls}&quot;&gt;&lt;td class=&quot;' + Ext.baseCSSPrefix + 'grid-cell&quot; colspan=&quot;' + parent.columns.length + '&quot; {[this.indentByDepth(values)]}&gt;&lt;div class=&quot;' + Ext.baseCSSPrefix + 'grid-cell-inner&quot;&gt;&lt;div class=&quot;' + Ext.baseCSSPrefix + 'grid-group-title&quot;&gt;{collapsed}' + me.groupHeaderTpl + '&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;',
195                 // this is the rowbody
196                 '&lt;tr id=&quot;{viewId}-gp-{name}&quot; class=&quot;' + Ext.baseCSSPrefix + 'grid-group-body ' + (me.startCollapsed ? me.collapsedCls : '') + ' {collapsedCls}&quot;&gt;&lt;td colspan=&quot;' + parent.columns.length + '&quot;&gt;{[this.recurse(values)]}&lt;/td&gt;&lt;/tr&gt;',
197             '&lt;/tpl&gt;'
198         ].join('');
199     },
200
201     getFragmentTpl: function() {
202         return {
203             indentByDepth: this.indentByDepth,
204             depthToIndent: this.depthToIndent
205         };
206     },
207
208     indentByDepth: function(values) {
209         var depth = values.depth || 0;
210         return 'style=&quot;padding-left:'+ depth * this.depthToIndent + 'px;&quot;';
211     },
212
213     // Containers holding these components are responsible for
214     // destroying them, we are just deleting references.
215     destroy: function() {
216         var me = this;
217         
218         delete me.view;
219         delete me.prunedHeader;
220     },
221
222     // perhaps rename to afterViewRender
223     attachEvents: function() {
224         var me = this,
225             view = me.view,
226             header, headerId, menu, menuItem;
227
228         view.on({
229             scope: me,
230             groupclick: me.onGroupClick,
231             rowfocus: me.onRowFocus
232         });
233         view.store.on('groupchange', me.onGroupChange, me);
234
235         me.pruneGroupedHeader();
236
237         if (me.enableGroupingMenu) {
238             me.injectGroupingMenu();
239         }
240
241         if (me.hideGroupedHeader) {
242             header = view.headerCt.down('gridcolumn[dataIndex=' + me.getGroupField() + ']');
243             headerId = header.id;
244             menu = view.headerCt.getMenu();
245             menuItem = menu.down('menuitem[headerId='+ headerId +']');
246             if (menuItem) {
247                 menuItem.setChecked(false);
248             }
249         }
250     },
251     
252     injectGroupingMenu: function() {
253         var me       = this,
254             view     = me.view,
255             headerCt = view.headerCt;
256         headerCt.showMenuBy = me.showMenuBy;
257         headerCt.getMenuItems = me.getMenuItems();
258     },
259     
260     showMenuBy: function(t, header) {
261         var menu = this.getMenu(),
262             groupMenuItem  = menu.down('#groupMenuItem'),
263             groupableMth = header.groupable === false ?  'disable' : 'enable';
264             
265         groupMenuItem[groupableMth]();
266         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
267     },
268     
269     getMenuItems: function() {
270         var me                 = this,
271             groupByText        = me.groupByText,
272             disabled           = me.disabled,
273             showGroupsText     = me.showGroupsText,
274             enableNoGroups     = me.enableNoGroups,
275             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
276             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
277         
278         // runs in the scope of headerCt
279         return function() {
280             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
281             o.push('-', {
282                 itemId: 'groupMenuItem',
283                 text: groupByText,
284                 handler: groupMenuItemClick
285             });
286             if (enableNoGroups) {
287                 o.push({
288                     itemId: 'groupToggleMenuItem',
289                     text: showGroupsText,
290                     checked: !disabled,
291                     checkHandler: groupToggleMenuItemClick
292                 });
293             }
294             return o;
295         };
296     },
297
298
299 <span id='Ext-grid-feature-Grouping-method-onGroupMenuItemClick'>    /**
300 </span>     * Group by the header the user has clicked on.
301      * @private
302      */
303     onGroupMenuItemClick: function(menuItem, e) {
304         var menu = menuItem.parentMenu,
305             hdr  = menu.activeHeader,
306             view = this.view;
307
308         delete this.lastGroupIndex;
309         this.enable();
310         view.store.group(hdr.dataIndex);
311         this.pruneGroupedHeader();
312         
313     },
314
315 <span id='Ext-grid-feature-Grouping-method-onGroupToggleMenuItemClick'>    /**
316 </span>     * Turn on and off grouping via the menu
317      * @private
318      */
319     onGroupToggleMenuItemClick: function(menuItem, checked) {
320         this[checked ? 'enable' : 'disable']();
321     },
322
323 <span id='Ext-grid-feature-Grouping-method-pruneGroupedHeader'>    /**
324 </span>     * Prunes the grouped header from the header container
325      * @private
326      */
327     pruneGroupedHeader: function() {
328         var me         = this,
329             view       = me.view,
330             store      = view.store,
331             groupField = me.getGroupField(),
332             headerCt   = view.headerCt,
333             header     = headerCt.down('header[dataIndex=' + groupField + ']');
334
335         if (header) {
336             if (me.prunedHeader) {
337                 me.prunedHeader.show();
338             }
339             me.prunedHeader = header;
340             header.hide();
341         }
342     },
343
344     getGroupField: function(){
345         var group = this.view.store.groupers.first();
346         if (group) {
347             return group.property;    
348         }
349         return ''; 
350     },
351
352 <span id='Ext-grid-feature-Grouping-method-onRowFocus'>    /**
353 </span>     * When a row gains focus, expand the groups above it
354      * @private
355      */
356     onRowFocus: function(rowIdx) {
357         var node    = this.view.getNode(rowIdx),
358             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
359
360         if (groupBd) {
361             // for multiple level groups, should expand every groupBd
362             // above
363             this.expand(groupBd);
364         }
365     },
366
367 <span id='Ext-grid-feature-Grouping-method-expand'>    /**
368 </span>     * Expand a group by the groupBody
369      * @param {Ext.core.Element} groupBd
370      * @private
371      */
372     expand: function(groupBd) {
373         var me = this,
374             view = me.view,
375             grid = view.up('gridpanel'),
376             groupBdDom = Ext.getDom(groupBd);
377             
378         me.collapsedState[groupBdDom.id] = false;
379
380         groupBd.removeCls(me.collapsedCls);
381         groupBd.prev().removeCls(me.hdCollapsedCls);
382
383         grid.determineScrollbars();
384         grid.invalidateScroller();
385         view.fireEvent('groupexpand');
386     },
387
388 <span id='Ext-grid-feature-Grouping-method-collapse'>    /**
389 </span>     * Collapse a group by the groupBody
390      * @param {Ext.core.Element} groupBd
391      * @private
392      */
393     collapse: function(groupBd) {
394         var me = this,
395             view = me.view,
396             grid = view.up('gridpanel'),
397             groupBdDom = Ext.getDom(groupBd);
398             
399         me.collapsedState[groupBdDom.id] = true;
400
401         groupBd.addCls(me.collapsedCls);
402         groupBd.prev().addCls(me.hdCollapsedCls);
403
404         grid.determineScrollbars();
405         grid.invalidateScroller();
406         view.fireEvent('groupcollapse');
407     },
408     
409     onGroupChange: function(){
410         this.view.refresh();
411     },
412
413 <span id='Ext-grid-feature-Grouping-method-onGroupClick'>    /**
414 </span>     * Toggle between expanded/collapsed state when clicking on
415      * the group.
416      * @private
417      */
418     onGroupClick: function(view, group, idx, foo, e) {
419         var me = this,
420             toggleCls = me.toggleCls,
421             groupBd = Ext.fly(group.nextSibling, '_grouping');
422
423         if (groupBd.hasCls(me.collapsedCls)) {
424             me.expand(groupBd);
425         } else {
426             me.collapse(groupBd);
427         }
428     },
429
430     // Injects isRow and closeRow into the metaRowTpl.
431     getMetaRowTplFragments: function() {
432         return {
433             isRow: this.isRow,
434             closeRow: this.closeRow
435         };
436     },
437
438     // injected into rowtpl and wrapped around metaRowTpl
439     // becomes part of the standard tpl
440     isRow: function() {
441         return '&lt;tpl if=&quot;typeof rows === \'undefined\'&quot;&gt;';
442     },
443
444     // injected into rowtpl and wrapped around metaRowTpl
445     // becomes part of the standard tpl
446     closeRow: function() {
447         return '&lt;/tpl&gt;';
448     },
449
450     // isRow and closeRow are injected via getMetaRowTplFragments
451     mutateMetaRowTpl: function(metaRowTpl) {
452         metaRowTpl.unshift('{[this.isRow()]}');
453         metaRowTpl.push('{[this.closeRow()]}');
454     },
455
456     // injects an additional style attribute via tdAttrKey with the proper
457     // amount of padding
458     getAdditionalData: function(data, idx, record, orig) {
459         var view = this.view,
460             hCt  = view.headerCt,
461             col  = hCt.items.getAt(0),
462             o = {},
463             tdAttrKey = col.id + '-tdAttr';
464
465         // maintain the current tdAttr that a user may ahve set.
466         o[tdAttrKey] = this.indentByDepth(data) + &quot; &quot; + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
467         o.collapsed = 'true';
468         return o;
469     },
470
471     // return matching preppedRecords
472     getGroupRows: function(group, records, preppedRecords, fullWidth) {
473         var me = this,
474             children = group.children,
475             rows = group.rows = [],
476             view = me.view;
477         group.viewId = view.id;
478
479         Ext.Array.each(records, function(record, idx) {
480             if (Ext.Array.indexOf(children, record) != -1) {
481                 rows.push(Ext.apply(preppedRecords[idx], {
482                     depth: 1
483                 }));
484             }
485         });
486         delete group.children;
487         group.fullWidth = fullWidth;
488         if (me.collapsedState[view.id + '-gp-' + group.name]) {
489             group.collapsedCls = me.collapsedCls;
490             group.hdCollapsedCls = me.hdCollapsedCls;
491         }
492
493         return group;
494     },
495
496     // return the data in a grouped format.
497     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
498         var me    = this,
499             store = me.view.store,
500             groups;
501             
502         if (!me.disabled &amp;&amp; store.isGrouped()) {
503             groups = store.getGroups();
504             Ext.Array.each(groups, function(group, idx){
505                 me.getGroupRows(group, records, preppedRecords, fullWidth);
506             }, me);
507             return {
508                 rows: groups,
509                 fullWidth: fullWidth
510             };
511         }
512         return o;
513     },
514     
515     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
516     // events that are fired on the view. Chose not to return the actual
517     // group itself because of its expense and because developers can simply
518     // grab the group via store.getGroups(groupName)
519     getFireEventArgs: function(type, view, featureTarget, e) {
520         var returnArray = [type, view, featureTarget],
521             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
522             groupBdId   = Ext.getDom(groupBd).id,
523             prefix      = view.id + '-gp-',
524             groupName   = groupBdId.substr(prefix.length);
525         
526         returnArray.push(groupName, e);
527         
528         return returnArray;
529     }
530 });
531 </pre>
532 </body>
533 </html>