/**
* @class Ext.grid.GroupingFeature
* @extends Ext.grid.Feature
*
* The Grouping Feature enhances a grid view's markup to support grouping at
* any arbitrary depth via recursion.
*
* Will expose additional events on the gridview with the prefix of 'group'.
* For example: 'groupclick', 'groupdblclick', 'groupcontextmenu'.
*
* @ftype grouping
*/
Ext.define('Ext.grid.GroupingFeature', {
extend: 'Ext.grid.Feature',
alias: 'feature.grouping',
eventPrefix: 'group',
eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
/**
* @event groupclick
* @param {Ext.grid.View} view
* @param {HTMLElement} node
* @param {Number} unused
* @param {Number} unused
* @param {Ext.EventObject} e
*/
/**
* @event groupdblclick
* @param {Ext.grid.View} view
* @param {HTMLElement} node
* @param {Number} unused
* @param {Number} unused
* @param {Ext.EventObject} e
*/
/**
* @event groupcontextmenu
* @param {Ext.grid.View} view
* @param {HTMLElement} node
* @param {Number} unused
* @param {Number} unused
* @param {Ext.EventObject} e
*/
/**
* @event groupcollapse
* @param {Ext.grid.View} view
* @param {HTMLElement} node
* @param {Number} unused
* @param {Number} unused
* @param {Ext.EventObject} e
*/
/**
* @event groupexpand
* @param {Ext.grid.View} view
* @param {HTMLElement} node
* @param {Number} unused
* @param {Number} unused
* @param {Ext.EventObject} e
*/
/**
* @cfg {String} groupHdTpl
* Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
* Defaults to 'Group: {name}'
*/
groupHdTpl: 'Group: {name}',
/**
* @cfg {Number} depthToIndent
* Number of pixels to indent per grouping level
*/
depthToIndent: 17,
collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
/**
* @cfg {String} groupByText Text displayed in the grid header menu for grouping by a column
* (defaults to 'Group By This Field').
*/
groupByText : 'Group By This Field',
/**
* @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
* (defaults to 'Show in Groups').
*/
showGroupsText : 'Show in Groups',
/**
* @cfg {Boolean} hideGroupedColumn true to hide the column that is currently grouped (defaults to false)
*/
hideGroupedColumn : false,
/**
* @cfg {Boolean} startCollapsed true to start all groups collapsed (defaults to false)
*/
startCollapsed : false,
/**
* @cfg {Boolean} enableGroupingMenu true to enable the grouping control in the column menu (defaults to true)
*/
enableGroupingMenu : true,
/**
* @cfg {Boolean} enableNoGroups true to allow the user to turn off grouping (defaults to true)
*/
enableNoGroups : true,
enable: function() {
Ext.grid.GroupingFeature.superclass.enable.call(this);
this.groupToggleMenuItem.setChecked(true, true);
},
disable: function() {
Ext.grid.GroupingFeature.superclass.disable.call(this);
this.groupToggleMenuItem.setChecked(false, true);
},
getFeatureTpl: function(values, parent, x, xcount) {
return [
'',
// group row tpl
'' + this.groupHdTpl + ' |
',
// this is the rowbody
'| {[this.recurse(values)]} |
',
''
].join('');
},
getTplFragments: function() {
return {
indentByDepth: this.indentByDepth,
depthToIndent: this.depthToIndent
};
},
indentByDepth: function(values) {
var depth = values.depth || 0;
return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
},
// Containers holding these components are responsible for
// destroying them, we are just deleting references.
destroy: function() {
delete this.groupMenuItem;
delete this.groupToggleMenuItem;
delete this.view;
delete this.prunedColumn;
},
// perhaps rename to afterViewRender
attachEvents: function() {
var view = this.view,
menu;
view.on('groupclick', this.onGroupClick, this);
view.on('rowfocus', this.onRowFocus, this);
this.pruneGroupedColumn();
if (this.enableGroupingMenu) {
menu = view.headerCt.getMenu();
menu.add('-');
this.groupMenuItem = menu.add({
text: this.groupByText,
handler: this.onGroupMenuItemClick,
scope: this
});
if (this.enableNoGroups) {
this.groupToggleMenuItem = menu.add({
text: this.showGroupsText,
checked: true,
checkHandler: this.onGroupToggleMenuItemClick,
scope: this
});
}
}
},
/**
* Group by the header the user has clicked on.
* @private
*/
onGroupMenuItemClick: function(menuItem, e) {
var menu = menuItem.parentMenu,
hdr = menu.activeHeader,
view = this.view;
view.store.groupField = hdr.dataIndex;
this.pruneGroupedColumn();
this.enable();
view.refresh();
},
/**
* Turn on and off grouping via the menu
* @private
*/
onGroupToggleMenuItemClick: function(menuItem, checked) {
this[checked ? 'enable' : 'disable']();
this.view.refresh();
},
/**
* Prunes the grouped column from the header container
* @private
*/
pruneGroupedColumn: function() {
var view = this.view,
store = view.store,
groupField = store.groupField,
headerCt = view.headerCt,
header = headerCt.down('header[dataIndex=' + groupField + ']');
if (header) {
if (this.prunedColumn) {
this.prunedColumn.show();
}
this.prunedColumn = header;
header.hide();
}
},
/**
* When a row gains focus, expand the groups above it
* @private
*/
onRowFocus: function(rowIdx) {
var node = this.view.getNode(rowIdx),
groupBd = Ext.fly(node).up('.' + this.collapsedCls);
if (groupBd) {
// for multiple level groups, should expand every groupBd
// above
this.expand(groupBd);
}
},
/**
* Expand a group by the groupBody
* @param {Ext.Element} groupBd
* @private
*/
expand: function(groupBd) {
var view = this.view,
grid = view.up('gridpanel');
groupBd.removeCls(this.collapsedCls);
groupBd.prev().removeCls(this.hdCollapsedCls);
grid.invalidateScroller();
view.fireEvent('groupexpand');
},
/**
* Collapse a group by the groupBody
* @param {Ext.Element} groupBd
* @private
*/
collapse: function(groupBd) {
var view = this.view,
grid = view.up('gridpanel');
groupBd.addCls(this.collapsedCls);
groupBd.prev().addCls(this.hdCollapsedCls);
grid.invalidateScroller();
view.fireEvent('groupcollapse');
},
/**
* Toggle between expanded/collapsed state when clicking on
* the group.
* @private
*/
onGroupClick: function(view, group, idx, foo, e) {
var toggleCls = this.toggleCls,
groupBd = Ext.fly(group.nextSibling, '_grouping');
if (groupBd.hasCls(this.collapsedCls)) {
this.expand(groupBd);
} else {
this.collapse(groupBd);
}
},
// Injects isRow and closeRow into the metaRowTpl.
getMetaRowTplFragments: function() {
return {
isRow: this.isRow,
closeRow: this.closeRow
};
},
// injected into rowtpl and wrapped around metaRowTpl
// becomes part of the standard tpl
isRow: function() {
return '';
},
// injected into rowtpl and wrapped around metaRowTpl
// becomes part of the standard tpl
closeRow: function() {
return '';
},
// isRow and closeRow are injected via getMetaRowTplFragments
mutateMetaRowTpl: function(metaRowTpl) {
metaRowTpl.unshift('{[this.isRow()]}');
metaRowTpl.push('{[this.closeRow()]}');
},
// injects an additional style attribute via tdAttrKey with the proper
// amount of padding
getAdditionalData: function(data, idx, record, orig) {
var view = this.view,
hCt = view.headerCt,
col = hCt.items.getAt(0),
o = {},
tdAttrKey = col.id + '-tdAttr';
// maintain the current tdAttr that a user may ahve set.
o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
return o;
},
//// return the data in a grouped format.
collectData: function(records, startIndex, fullWidth, orig) {
if (this.disabled) {
return orig;
}
var view = this.view,
groups = view.store.getGroups(),
ln = groups.length,
i = 0;
for (; i < ln; i++) {
groups[i].fullWidth = fullWidth;
// need additional check to determine if this is a group or not
// that way we can invoke GridView directly.
groups[i].rows = Ext.grid.View.superclass.collectData.call(this.view, groups[i].children, 0);
for (j = 0, jln = groups[i].rows.length; j < jln; j++) {
groups[i].rows[j].depth = 1;
}
delete groups[i].children;
}
return {
rows: groups,
fullWidth: fullWidth
};
}
});