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() {
61 me.collapsedState = {};
62 me.callParent(arguments);
67 * @param {Ext.view.Table} view
68 * @param {HTMLElement} node
69 * @param {String} group The name of the group
70 * @param {Ext.EventObject} e
74 * @event groupdblclick
75 * @param {Ext.view.Table} view
76 * @param {HTMLElement} node
77 * @param {String} group The name of the group
78 * @param {Ext.EventObject} e
82 * @event groupcontextmenu
83 * @param {Ext.view.Table} view
84 * @param {HTMLElement} node
85 * @param {String} group The name of the group
86 * @param {Ext.EventObject} e
90 * @event groupcollapse
91 * @param {Ext.view.Table} view
92 * @param {HTMLElement} node
93 * @param {String} group The name of the group
94 * @param {Ext.EventObject} e
99 * @param {Ext.view.Table} view
100 * @param {HTMLElement} node
101 * @param {String} group The name of the group
102 * @param {Ext.EventObject} e
106 * @cfg {String} groupHeaderTpl
107 * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
108 * Defaults to 'Group: {name}'
110 groupHeaderTpl: 'Group: {name}',
113 * @cfg {Number} depthToIndent
114 * Number of pixels to indent per grouping level
118 collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
119 hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
122 * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header.
124 groupByText : 'Group By This Field',
126 * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping.
128 showGroupsText : 'Show in Groups',
131 * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped.
133 hideGroupedHeader : false,
136 * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed
138 startCollapsed : false,
141 * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu
143 enableGroupingMenu : true,
146 * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping
148 enableNoGroups : true,
156 me.lastGroupField = me.getGroupField();
158 if (me.lastGroupIndex) {
159 store.group(me.lastGroupIndex);
162 groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
163 groupToggleMenuItem.setChecked(true, true);
167 disable: function() {
171 remote = store.remoteGroup,
175 lastGroup = store.groupers.first();
177 me.lastGroupIndex = lastGroup.property;
179 store.clearGrouping();
184 groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
185 groupToggleMenuItem.setChecked(true, true);
186 groupToggleMenuItem.setChecked(false, true);
192 refreshIf: function() {
193 if (this.blockRefresh !== true) {
198 getFeatureTpl: function(values, parent, x, xcount) {
202 '<tpl if="typeof rows !== \'undefined\'">',
204 '<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>',
205 // this is the rowbody
206 '<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>',
211 getFragmentTpl: function() {
213 indentByDepth: this.indentByDepth,
214 depthToIndent: this.depthToIndent
218 indentByDepth: function(values) {
219 var depth = values.depth || 0;
220 return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
223 // Containers holding these components are responsible for
224 // destroying them, we are just deleting references.
225 destroy: function() {
229 delete me.prunedHeader;
232 // perhaps rename to afterViewRender
233 attachEvents: function() {
239 groupclick: me.onGroupClick,
240 rowfocus: me.onRowFocus
242 view.store.on('groupchange', me.onGroupChange, me);
244 me.pruneGroupedHeader();
246 if (me.enableGroupingMenu) {
247 me.injectGroupingMenu();
249 me.lastGroupField = me.getGroupField();
255 injectGroupingMenu: function() {
258 headerCt = view.headerCt;
259 headerCt.showMenuBy = me.showMenuBy;
260 headerCt.getMenuItems = me.getMenuItems();
263 showMenuBy: function(t, header) {
264 var menu = this.getMenu(),
265 groupMenuItem = menu.down('#groupMenuItem'),
266 groupableMth = header.groupable === false ? 'disable' : 'enable';
268 groupMenuItem[groupableMth]();
269 Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
272 getMenuItems: function() {
274 groupByText = me.groupByText,
275 disabled = me.disabled,
276 showGroupsText = me.showGroupsText,
277 enableNoGroups = me.enableNoGroups,
278 groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
279 groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
281 // runs in the scope of headerCt
283 var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
285 iconCls: Ext.baseCSSPrefix + 'group-by-icon',
286 itemId: 'groupMenuItem',
288 handler: groupMenuItemClick
290 if (enableNoGroups) {
292 itemId: 'groupToggleMenuItem',
293 text: showGroupsText,
295 checkHandler: groupToggleMenuItemClick
304 * Group by the header the user has clicked on.
307 onGroupMenuItemClick: function(menuItem, e) {
309 menu = menuItem.parentMenu,
310 hdr = menu.activeHeader,
313 remote = store.remoteGroup;
315 delete me.lastGroupIndex;
318 store.group(hdr.dataIndex);
319 me.pruneGroupedHeader();
327 this.blockRefresh = this.view.blockRefresh = true;
331 this.blockRefresh = this.view.blockRefresh = false;
335 * Turn on and off grouping via the menu
338 onGroupToggleMenuItemClick: function(menuItem, checked) {
339 this[checked ? 'enable' : 'disable']();
343 * Prunes the grouped header from the header container
346 pruneGroupedHeader: function() {
350 groupField = me.getGroupField(),
351 headerCt = view.headerCt,
352 header = headerCt.down('header[dataIndex=' + groupField + ']');
355 if (me.prunedHeader) {
356 me.prunedHeader.show();
358 me.prunedHeader = header;
363 getGroupField: function(){
364 var group = this.view.store.groupers.first();
366 return group.property;
372 * When a row gains focus, expand the groups above it
375 onRowFocus: function(rowIdx) {
376 var node = this.view.getNode(rowIdx),
377 groupBd = Ext.fly(node).up('.' + this.collapsedCls);
380 // for multiple level groups, should expand every groupBd
382 this.expand(groupBd);
387 * Expand a group by the groupBody
388 * @param {Ext.Element} groupBd
391 expand: function(groupBd) {
394 grid = view.up('gridpanel'),
395 groupBdDom = Ext.getDom(groupBd);
397 me.collapsedState[groupBdDom.id] = false;
399 groupBd.removeCls(me.collapsedCls);
400 groupBd.prev().removeCls(me.hdCollapsedCls);
402 grid.determineScrollbars();
403 grid.invalidateScroller();
404 view.fireEvent('groupexpand');
408 * Collapse a group by the groupBody
409 * @param {Ext.Element} groupBd
412 collapse: function(groupBd) {
415 grid = view.up('gridpanel'),
416 groupBdDom = Ext.getDom(groupBd);
418 me.collapsedState[groupBdDom.id] = true;
420 groupBd.addCls(me.collapsedCls);
421 groupBd.prev().addCls(me.hdCollapsedCls);
423 grid.determineScrollbars();
424 grid.invalidateScroller();
425 view.fireEvent('groupcollapse');
428 onGroupChange: function(){
430 field = me.getGroupField(),
433 if (me.hideGroupedHeader) {
434 if (me.lastGroupField) {
435 menuItem = me.getMenuItem(me.lastGroupField);
437 menuItem.setChecked(true);
441 menuItem = me.getMenuItem(field);
443 menuItem.setChecked(false);
447 if (me.blockRefresh !== true) {
450 me.lastGroupField = field;
454 * Gets the related menu item for a dataIndex
456 * @return {Ext.grid.header.Container} The header
458 getMenuItem: function(dataIndex){
459 var view = this.view,
460 header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
461 menu = view.headerCt.getMenu();
463 return menu.down('menuitem[headerId='+ header.id +']');
467 * Toggle between expanded/collapsed state when clicking on
471 onGroupClick: function(view, group, idx, foo, e) {
473 toggleCls = me.toggleCls,
474 groupBd = Ext.fly(group.nextSibling, '_grouping');
476 if (groupBd.hasCls(me.collapsedCls)) {
479 me.collapse(groupBd);
483 // Injects isRow and closeRow into the metaRowTpl.
484 getMetaRowTplFragments: function() {
487 closeRow: this.closeRow
491 // injected into rowtpl and wrapped around metaRowTpl
492 // becomes part of the standard tpl
494 return '<tpl if="typeof rows === \'undefined\'">';
497 // injected into rowtpl and wrapped around metaRowTpl
498 // becomes part of the standard tpl
499 closeRow: function() {
503 // isRow and closeRow are injected via getMetaRowTplFragments
504 mutateMetaRowTpl: function(metaRowTpl) {
505 metaRowTpl.unshift('{[this.isRow()]}');
506 metaRowTpl.push('{[this.closeRow()]}');
509 // injects an additional style attribute via tdAttrKey with the proper
511 getAdditionalData: function(data, idx, record, orig) {
512 var view = this.view,
514 col = hCt.items.getAt(0),
516 tdAttrKey = col.id + '-tdAttr';
518 // maintain the current tdAttr that a user may ahve set.
519 o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
520 o.collapsed = 'true';
524 // return matching preppedRecords
525 getGroupRows: function(group, records, preppedRecords, fullWidth) {
527 children = group.children,
528 rows = group.rows = [],
530 group.viewId = view.id;
532 Ext.Array.each(records, function(record, idx) {
533 if (Ext.Array.indexOf(children, record) != -1) {
534 rows.push(Ext.apply(preppedRecords[idx], {
539 delete group.children;
540 group.fullWidth = fullWidth;
541 if (me.collapsedState[view.id + '-gp-' + group.name]) {
542 group.collapsedCls = me.collapsedCls;
543 group.hdCollapsedCls = me.hdCollapsedCls;
549 // return the data in a grouped format.
550 collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
552 store = me.view.store,
555 if (!me.disabled && store.isGrouped()) {
556 groups = store.getGroups();
557 Ext.Array.each(groups, function(group, idx){
558 me.getGroupRows(group, records, preppedRecords, fullWidth);
568 // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
569 // events that are fired on the view. Chose not to return the actual
570 // group itself because of its expense and because developers can simply
571 // grab the group via store.getGroups(groupName)
572 getFireEventArgs: function(type, view, featureTarget, e) {
573 var returnArray = [type, view, featureTarget],
574 groupBd = Ext.fly(featureTarget.nextSibling, '_grouping'),
575 groupBdId = Ext.getDom(groupBd).id,
576 prefix = view.id + '-gp-',
577 groupName = groupBdId.substr(prefix.length);
579 returnArray.push(groupName, e);