3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.grid.feature.Grouping
17 * @extends Ext.grid.feature.Feature
19 * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
20 * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
21 * underneath. The groups can also be expanded and collapsed.
24 * This feature adds several extra events that will be fired on the grid to interact with the groups:
26 * - {@link #groupclick}
27 * - {@link #groupdblclick}
28 * - {@link #groupcontextmenu}
29 * - {@link #groupexpand}
30 * - {@link #groupcollapse}
32 * ## Menu Augmentation
33 * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
34 * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
35 * by thew user is {@link #enableNoGroups}.
37 * ## Controlling Group Text
38 * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
39 * the default display.
43 * var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
44 * groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
45 * startCollapsed: true // start all groups collapsed
49 * @author Nicolas Ferrero
51 Ext.define('Ext.grid.feature.Grouping', {
52 extend: 'Ext.grid.feature.Feature',
53 alias: 'feature.grouping',
56 eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
58 constructor: function() {
59 this.collapsedState = {};
60 this.callParent(arguments);
65 * @param {Ext.view.Table} view
66 * @param {HTMLElement} node
67 * @param {String} group The name of the group
68 * @param {Ext.EventObject} e
72 * @event groupdblclick
73 * @param {Ext.view.Table} view
74 * @param {HTMLElement} node
75 * @param {String} group The name of the group
76 * @param {Ext.EventObject} e
80 * @event groupcontextmenu
81 * @param {Ext.view.Table} view
82 * @param {HTMLElement} node
83 * @param {String} group The name of the group
84 * @param {Ext.EventObject} e
88 * @event groupcollapse
89 * @param {Ext.view.Table} view
90 * @param {HTMLElement} node
91 * @param {String} group The name of the group
92 * @param {Ext.EventObject} e
97 * @param {Ext.view.Table} view
98 * @param {HTMLElement} node
99 * @param {String} group The name of the group
100 * @param {Ext.EventObject} e
104 * @cfg {String} groupHeaderTpl
105 * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
106 * Defaults to 'Group: {name}'
108 groupHeaderTpl: 'Group: {name}',
111 * @cfg {Number} depthToIndent
112 * Number of pixels to indent per grouping level
116 collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
117 hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
120 * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header
121 * (defaults to 'Group By This Field').
123 groupByText : 'Group By This Field',
125 * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
126 * (defaults to 'Show in Groups').
128 showGroupsText : 'Show in Groups',
131 * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped (defaults to <tt>false</tt>)
133 hideGroupedHeader : false,
136 * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed (defaults to <tt>false</tt>)
138 startCollapsed : false,
141 * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu (defaults to <tt>true</tt>)
143 enableGroupingMenu : true,
146 * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping (defaults to <tt>true</tt>)
148 enableNoGroups : true,
156 if (me.lastGroupIndex) {
157 store.group(me.lastGroupIndex);
160 groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
161 groupToggleMenuItem.setChecked(true, true);
165 disable: function() {
172 lastGroup = store.groupers.first();
174 me.lastGroupIndex = lastGroup.property;
175 store.groupers.clear();
179 groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
180 groupToggleMenuItem.setChecked(true, true);
181 groupToggleMenuItem.setChecked(false, true);
185 getFeatureTpl: function(values, parent, x, xcount) {
189 '<tpl if="typeof rows !== \'undefined\'">',
191 '<tr class="' + Ext.baseCSSPrefix + 'grid-group-hd ' + (me.startCollapsed ? me.hdCollapsedCls : '') + ' {hdCollapsedCls}"><td class="' + Ext.baseCSSPrefix + 'grid-cell" colspan="' + parent.columns.length + '" {[this.indentByDepth(values)]}><div class="' + Ext.baseCSSPrefix + 'grid-cell-inner"><div class="' + Ext.baseCSSPrefix + 'grid-group-title">{collapsed}' + me.groupHeaderTpl + '</div></div></td></tr>',
192 // this is the rowbody
193 '<tr id="{viewId}-gp-{name}" class="' + Ext.baseCSSPrefix + 'grid-group-body ' + (me.startCollapsed ? me.collapsedCls : '') + ' {collapsedCls}"><td colspan="' + parent.columns.length + '">{[this.recurse(values)]}</td></tr>',
198 getFragmentTpl: function() {
200 indentByDepth: this.indentByDepth,
201 depthToIndent: this.depthToIndent
205 indentByDepth: function(values) {
206 var depth = values.depth || 0;
207 return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
210 // Containers holding these components are responsible for
211 // destroying them, we are just deleting references.
212 destroy: function() {
216 delete me.prunedHeader;
219 // perhaps rename to afterViewRender
220 attachEvents: function() {
223 header, headerId, menu, menuItem;
227 groupclick: me.onGroupClick,
228 rowfocus: me.onRowFocus
230 view.store.on('groupchange', me.onGroupChange, me);
232 me.pruneGroupedHeader();
234 if (me.enableGroupingMenu) {
235 me.injectGroupingMenu();
238 if (me.hideGroupedHeader) {
239 header = view.headerCt.down('gridcolumn[dataIndex=' + me.getGroupField() + ']');
240 headerId = header.id;
241 menu = view.headerCt.getMenu();
242 menuItem = menu.down('menuitem[headerId='+ headerId +']');
244 menuItem.setChecked(false);
249 injectGroupingMenu: function() {
252 headerCt = view.headerCt;
253 headerCt.showMenuBy = me.showMenuBy;
254 headerCt.getMenuItems = me.getMenuItems();
257 showMenuBy: function(t, header) {
258 var menu = this.getMenu(),
259 groupMenuItem = menu.down('#groupMenuItem'),
260 groupableMth = header.groupable === false ? 'disable' : 'enable';
262 groupMenuItem[groupableMth]();
263 Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
266 getMenuItems: function() {
268 groupByText = me.groupByText,
269 disabled = me.disabled,
270 showGroupsText = me.showGroupsText,
271 enableNoGroups = me.enableNoGroups,
272 groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
273 groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
275 // runs in the scope of headerCt
277 var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
279 itemId: 'groupMenuItem',
281 handler: groupMenuItemClick
283 if (enableNoGroups) {
285 itemId: 'groupToggleMenuItem',
286 text: showGroupsText,
288 checkHandler: groupToggleMenuItemClick
297 * Group by the header the user has clicked on.
300 onGroupMenuItemClick: function(menuItem, e) {
301 var menu = menuItem.parentMenu,
302 hdr = menu.activeHeader,
305 delete this.lastGroupIndex;
307 view.store.group(hdr.dataIndex);
308 this.pruneGroupedHeader();
313 * Turn on and off grouping via the menu
316 onGroupToggleMenuItemClick: function(menuItem, checked) {
317 this[checked ? 'enable' : 'disable']();
321 * Prunes the grouped header from the header container
324 pruneGroupedHeader: function() {
328 groupField = me.getGroupField(),
329 headerCt = view.headerCt,
330 header = headerCt.down('header[dataIndex=' + groupField + ']');
333 if (me.prunedHeader) {
334 me.prunedHeader.show();
336 me.prunedHeader = header;
341 getGroupField: function(){
342 var group = this.view.store.groupers.first();
344 return group.property;
350 * When a row gains focus, expand the groups above it
353 onRowFocus: function(rowIdx) {
354 var node = this.view.getNode(rowIdx),
355 groupBd = Ext.fly(node).up('.' + this.collapsedCls);
358 // for multiple level groups, should expand every groupBd
360 this.expand(groupBd);
365 * Expand a group by the groupBody
366 * @param {Ext.core.Element} groupBd
369 expand: function(groupBd) {
372 grid = view.up('gridpanel'),
373 groupBdDom = Ext.getDom(groupBd);
375 me.collapsedState[groupBdDom.id] = false;
377 groupBd.removeCls(me.collapsedCls);
378 groupBd.prev().removeCls(me.hdCollapsedCls);
380 grid.determineScrollbars();
381 grid.invalidateScroller();
382 view.fireEvent('groupexpand');
386 * Collapse a group by the groupBody
387 * @param {Ext.core.Element} groupBd
390 collapse: function(groupBd) {
393 grid = view.up('gridpanel'),
394 groupBdDom = Ext.getDom(groupBd);
396 me.collapsedState[groupBdDom.id] = true;
398 groupBd.addCls(me.collapsedCls);
399 groupBd.prev().addCls(me.hdCollapsedCls);
401 grid.determineScrollbars();
402 grid.invalidateScroller();
403 view.fireEvent('groupcollapse');
406 onGroupChange: function(){
411 * Toggle between expanded/collapsed state when clicking on
415 onGroupClick: function(view, group, idx, foo, e) {
417 toggleCls = me.toggleCls,
418 groupBd = Ext.fly(group.nextSibling, '_grouping');
420 if (groupBd.hasCls(me.collapsedCls)) {
423 me.collapse(groupBd);
427 // Injects isRow and closeRow into the metaRowTpl.
428 getMetaRowTplFragments: function() {
431 closeRow: this.closeRow
435 // injected into rowtpl and wrapped around metaRowTpl
436 // becomes part of the standard tpl
438 return '<tpl if="typeof rows === \'undefined\'">';
441 // injected into rowtpl and wrapped around metaRowTpl
442 // becomes part of the standard tpl
443 closeRow: function() {
447 // isRow and closeRow are injected via getMetaRowTplFragments
448 mutateMetaRowTpl: function(metaRowTpl) {
449 metaRowTpl.unshift('{[this.isRow()]}');
450 metaRowTpl.push('{[this.closeRow()]}');
453 // injects an additional style attribute via tdAttrKey with the proper
455 getAdditionalData: function(data, idx, record, orig) {
456 var view = this.view,
458 col = hCt.items.getAt(0),
460 tdAttrKey = col.id + '-tdAttr';
462 // maintain the current tdAttr that a user may ahve set.
463 o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
464 o.collapsed = 'true';
468 // return matching preppedRecords
469 getGroupRows: function(group, records, preppedRecords, fullWidth) {
471 children = group.children,
472 rows = group.rows = [],
474 group.viewId = view.id;
476 Ext.Array.each(records, function(record, idx) {
477 if (Ext.Array.indexOf(children, record) != -1) {
478 rows.push(Ext.apply(preppedRecords[idx], {
483 delete group.children;
484 group.fullWidth = fullWidth;
485 if (me.collapsedState[view.id + '-gp-' + group.name]) {
486 group.collapsedCls = me.collapsedCls;
487 group.hdCollapsedCls = me.hdCollapsedCls;
493 // return the data in a grouped format.
494 collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
496 store = me.view.store,
499 if (!me.disabled && store.isGrouped()) {
500 groups = store.getGroups();
501 Ext.Array.each(groups, function(group, idx){
502 me.getGroupRows(group, records, preppedRecords, fullWidth);
512 // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
513 // events that are fired on the view. Chose not to return the actual
514 // group itself because of its expense and because developers can simply
515 // grab the group via store.getGroups(groupName)
516 getFireEventArgs: function(type, view, featureTarget, e) {
517 var returnArray = [type, view, featureTarget],
518 groupBd = Ext.fly(featureTarget.nextSibling, '_grouping'),
519 groupBdId = Ext.getDom(groupBd).id,
520 prefix = view.id + '-gp-',
521 groupName = groupBdId.substr(prefix.length);
523 returnArray.push(groupName, e);