<!DOCTYPE html>
|
<html>
|
<head>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<title>The source code</title>
|
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
|
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
|
<style type="text/css">
|
.highlight { display: block; background-color: #ddd; }
|
</style>
|
<script type="text/javascript">
|
function highlight() {
|
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
|
}
|
</script>
|
</head>
|
<body onload="prettyPrint(); highlight();">
|
<pre class="prettyprint lang-js"><span id='Ext-grid-feature-Grouping'>/**
|
</span> * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
|
* specified on the Store. The group will show the title for the group name and then the appropriate records for the group
|
* underneath. The groups can also be expanded and collapsed.
|
*
|
* ## Extra Events
|
*
|
* This feature adds several extra events that will be fired on the grid to interact with the groups:
|
*
|
* - {@link #groupclick}
|
* - {@link #groupdblclick}
|
* - {@link #groupcontextmenu}
|
* - {@link #groupexpand}
|
* - {@link #groupcollapse}
|
*
|
* ## Menu Augmentation
|
*
|
* This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
|
* This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
|
* by the user is {@link #enableNoGroups}.
|
*
|
* ## Controlling Group Text
|
*
|
* The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
|
* the default display.
|
*
|
* ## Example Usage
|
*
|
* @example
|
* var store = Ext.create('Ext.data.Store', {
|
* storeId:'employeeStore',
|
* fields:['name', 'seniority', 'department'],
|
* groupField: 'department',
|
* data: {'employees':[
|
* { "name": "Michael Scott", "seniority": 7, "department": "Management" },
|
* { "name": "Dwight Schrute", "seniority": 2, "department": "Sales" },
|
* { "name": "Jim Halpert", "seniority": 3, "department": "Sales" },
|
* { "name": "Kevin Malone", "seniority": 4, "department": "Accounting" },
|
* { "name": "Angela Martin", "seniority": 5, "department": "Accounting" }
|
* ]},
|
* proxy: {
|
* type: 'memory',
|
* reader: {
|
* type: 'json',
|
* root: 'employees'
|
* }
|
* }
|
* });
|
*
|
* Ext.create('Ext.grid.Panel', {
|
* title: 'Employees',
|
* store: Ext.data.StoreManager.lookup('employeeStore'),
|
* columns: [
|
* { text: 'Name', dataIndex: 'name' },
|
* { text: 'Seniority', dataIndex: 'seniority' }
|
* ],
|
* features: [{ftype:'grouping'}],
|
* width: 200,
|
* height: 275,
|
* renderTo: Ext.getBody()
|
* });
|
*
|
* **Note:** To use grouping with a grid that has {@link Ext.grid.column.Column#locked locked columns}, you need to supply
|
* the grouping feature as a config object - so the grid can create two instances of the grouping feature.
|
*
|
* @author Nigel White
|
*/
|
Ext.define('Ext.grid.feature.Grouping', {
|
extend: 'Ext.grid.feature.Feature',
|
mixins: {
|
summary: 'Ext.grid.feature.AbstractSummary'
|
},
|
requires: ['Ext.grid.feature.GroupStore'],
|
|
alias: 'feature.grouping',
|
|
<span id='Ext-grid-feature-Grouping-property-eventPrefix'> eventPrefix: 'group',
|
</span><span id='Ext-grid-feature-Grouping-property-groupCls'> groupCls: Ext.baseCSSPrefix + 'grid-group-hd',
|
</span><span id='Ext-grid-feature-Grouping-property-eventSelector'> eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
|
</span>
|
<span id='Ext-grid-feature-Grouping-property-refreshData'> refreshData: {},
|
</span><span id='Ext-grid-feature-Grouping-property-groupInfo'> groupInfo: {},
|
</span><span id='Ext-grid-feature-Grouping-property-wrapsItem'> wrapsItem: true,
|
</span>
|
<span id='Ext-grid-feature-Grouping-event-groupclick'> /**
|
</span> * @event groupclick
|
* @param {Ext.view.Table} view
|
* @param {HTMLElement} node
|
* @param {String} group The name of the group
|
* @param {Ext.EventObject} e
|
*/
|
|
<span id='Ext-grid-feature-Grouping-event-groupdblclick'> /**
|
</span> * @event groupdblclick
|
* @param {Ext.view.Table} view
|
* @param {HTMLElement} node
|
* @param {String} group The name of the group
|
* @param {Ext.EventObject} e
|
*/
|
|
<span id='Ext-grid-feature-Grouping-event-groupcontextmenu'> /**
|
</span> * @event groupcontextmenu
|
* @param {Ext.view.Table} view
|
* @param {HTMLElement} node
|
* @param {String} group The name of the group
|
* @param {Ext.EventObject} e
|
*/
|
|
<span id='Ext-grid-feature-Grouping-event-groupcollapse'> /**
|
</span> * @event groupcollapse
|
* @param {Ext.view.Table} view
|
* @param {HTMLElement} node
|
* @param {String} group The name of the group
|
*/
|
|
<span id='Ext-grid-feature-Grouping-event-groupexpand'> /**
|
</span> * @event groupexpand
|
* @param {Ext.view.Table} view
|
* @param {HTMLElement} node
|
* @param {String} group The name of the group
|
*/
|
|
<span id='Ext-grid-feature-Grouping-cfg-groupHeaderTpl'> /**
|
</span> * @cfg {String/Array/Ext.Template} groupHeaderTpl
|
* A string Template snippet, an array of strings (optionally followed by an object containing Template methods) to be used to construct a Template, or a Template instance.
|
*
|
* - Example 1 (Template snippet):
|
*
|
* groupHeaderTpl: 'Group: {name}'
|
*
|
* - Example 2 (Array):
|
*
|
* groupHeaderTpl: [
|
* 'Group: ',
|
* '<div>{name:this.formatName}</div>',
|
* {
|
* formatName: function(name) {
|
* return Ext.String.trim(name);
|
* }
|
* }
|
* ]
|
*
|
* - Example 3 (Template Instance):
|
*
|
* groupHeaderTpl: Ext.create('Ext.XTemplate',
|
* 'Group: ',
|
* '<div>{name:this.formatName}</div>',
|
* {
|
* formatName: function(name) {
|
* return Ext.String.trim(name);
|
* }
|
* }
|
* )
|
*
|
* @cfg {String} groupHeaderTpl.groupField The field name being grouped by.
|
* @cfg {String} groupHeaderTpl.columnName The column header associated with the field being grouped by *if there is a column for the field*, falls back to the groupField name.
|
* @cfg {Mixed} groupHeaderTpl.groupValue The value of the {@link Ext.data.Store#groupField groupField} for the group header being rendered.
|
* @cfg {String} groupHeaderTpl.renderedGroupValue The rendered value of the {@link Ext.data.Store#groupField groupField} for the group header being rendered, as produced by the column renderer.
|
* @cfg {String} groupHeaderTpl.name An alias for renderedGroupValue
|
* @cfg {Ext.data.Model[]} groupHeaderTpl.rows Deprecated - use children instead. An array containing the child records for the group being rendered. *Not available if the store is {@link Ext.data.Store#buffered buffered}*
|
* @cfg {Ext.data.Model[]} groupHeaderTpl.children An array containing the child records for the group being rendered. *Not available if the store is {@link Ext.data.Store#buffered buffered}*
|
*/
|
groupHeaderTpl: '{columnName}: {name}',
|
|
<span id='Ext-grid-feature-Grouping-cfg-depthToIndent'> /**
|
</span> * @cfg {Number} [depthToIndent=17]
|
* Number of pixels to indent per grouping level
|
*/
|
depthToIndent: 17,
|
|
<span id='Ext-grid-feature-Grouping-property-collapsedCls'> collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
|
</span><span id='Ext-grid-feature-Grouping-property-hdCollapsedCls'> hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
|
</span><span id='Ext-grid-feature-Grouping-property-hdNotCollapsibleCls'> hdNotCollapsibleCls: Ext.baseCSSPrefix + 'grid-group-hd-not-collapsible',
|
</span><span id='Ext-grid-feature-Grouping-property-collapsibleCls'> collapsibleCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsible',
|
</span><span id='Ext-grid-feature-Grouping-property-ctCls'> ctCls: Ext.baseCSSPrefix + 'group-hd-container',
|
</span>
|
//<locale>
|
<span id='Ext-grid-feature-Grouping-cfg-groupByText'> /**
|
</span> * @cfg {String} [groupByText="Group by this field"]
|
* Text displayed in the grid header menu for grouping by header.
|
*/
|
groupByText : 'Group by this field',
|
//</locale>
|
//<locale>
|
<span id='Ext-grid-feature-Grouping-cfg-showGroupsText'> /**
|
</span> * @cfg {String} [showGroupsText="Show in groups"]
|
* Text displayed in the grid header for enabling/disabling grouping.
|
*/
|
showGroupsText : 'Show in groups',
|
//</locale>
|
|
<span id='Ext-grid-feature-Grouping-cfg-hideGroupedHeader'> /**
|
</span> * @cfg {Boolean} [hideGroupedHeader=false]
|
* True to hide the header that is currently grouped.
|
*/
|
hideGroupedHeader : false,
|
|
<span id='Ext-grid-feature-Grouping-cfg-startCollapsed'> /**
|
</span> * @cfg {Boolean} [startCollapsed=false]
|
* True to start all groups collapsed.
|
*/
|
startCollapsed : false,
|
|
<span id='Ext-grid-feature-Grouping-cfg-enableGroupingMenu'> /**
|
</span> * @cfg {Boolean} [enableGroupingMenu=true]
|
* True to enable the grouping control in the header menu.
|
*/
|
enableGroupingMenu : true,
|
|
<span id='Ext-grid-feature-Grouping-cfg-enableNoGroups'> /**
|
</span> * @cfg {Boolean} [enableNoGroups=true]
|
* True to allow the user to turn off grouping.
|
*/
|
enableNoGroups : true,
|
|
<span id='Ext-grid-feature-Grouping-cfg-collapsible'> /**
|
</span> * @cfg {Boolean} [collapsible=true]
|
* Set to `false` to disable collapsing groups from the UI.
|
*
|
* This is set to `false` when the associated {@link Ext.data.Store store} is
|
* {@link Ext.data.Store#buffered buffered}.
|
*/
|
collapsible: true,
|
|
<span id='Ext-grid-feature-Grouping-property-expandTip'> //<locale>
|
</span> expandTip: 'Click to expand. CTRL key collapses all others',
|
//</locale>
|
|
<span id='Ext-grid-feature-Grouping-property-collapseTip'> //<locale>
|
</span> collapseTip: 'Click to collapse. CTRL/click collapses all others',
|
<span id='Ext-grid-feature-Grouping-cfg-showSummaryRow'> //</locale>
|
</span>
|
showSummaryRow: false,
|
|
<span id='Ext-grid-feature-Grouping-property-tableTpl'> tableTpl: {
|
</span> before: function(values) {
|
// Do not process if we are disabled, and do not process summary records
|
if (this.groupingFeature.disabled || values.rows.length === 1 && values.rows[0].isSummary) {
|
return;
|
}
|
this.groupingFeature.setup(values.rows, values.view.rowValues);
|
},
|
after: function(values) {
|
// Do not process if we are disabled, and do not process summary records
|
if (this.groupingFeature.disabled || values.rows.length === 1 && values.rows[0].isSummary) {
|
return;
|
}
|
this.groupingFeature.cleanup(values.rows, values.view.rowValues);
|
},
|
priority: 200
|
},
|
|
<span id='Ext-grid-feature-Grouping-property-groupTpl'> groupTpl: [
|
</span> '{%',
|
'var me = this.groupingFeature;',
|
// If grouping is disabled, do not call setupRowData, and do not wrap
|
'if (me.disabled) {',
|
'values.needsWrap = false;',
|
'} else {',
|
'me.setupRowData(values.record, values.recordIndex, values);',
|
'values.needsWrap = !me.disabled && (values.isFirstRow || values.summaryRecord);',
|
'}',
|
'%}',
|
'<tpl if="needsWrap">',
|
'<tr data-boundView="{view.id}" data-recordId="{record.internalId}" data-recordIndex="{[values.isCollapsedGroup ? -1 : values.recordIndex]}"',
|
'class="{[values.itemClasses.join(" ")]} ' + Ext.baseCSSPrefix + 'grid-wrap-row<tpl if="!summaryRecord"> ' + Ext.baseCSSPrefix + 'grid-group-row</tpl>">',
|
'<td class="' + Ext.baseCSSPrefix + 'group-hd-container" colspan="{columns.length}">',
|
'<tpl if="isFirstRow">',
|
'{%',
|
// Group title is visible if not locking, or we are the locked side, or the locked side has no columns/
|
// Use visibility to keep row heights synced without intervention.
|
'var groupTitleStyle = (!values.view.lockingPartner || (values.view.ownerCt === values.view.ownerCt.ownerLockable.lockedGrid) || (values.view.lockingPartner.headerCt.getVisibleGridColumns().length === 0)) ? "" : "visibility:hidden";',
|
'%}',
|
'<div id="{groupId}" class="' + Ext.baseCSSPrefix + 'grid-group-hd {collapsibleCls}" tabIndex="0">',
|
'<div class="' + Ext.baseCSSPrefix + 'grid-group-title" style="{[groupTitleStyle]}">',
|
'{[values.groupHeaderTpl.apply(values.groupInfo, parent) || "&#160;"]}',
|
'</div>',
|
'</div>',
|
'</tpl>',
|
|
// Only output the child rows if this is *not* a collapsed group
|
'<tpl if="summaryRecord || !isCollapsedGroup">',
|
'<table class="', Ext.baseCSSPrefix, '{view.id}-table ', Ext.baseCSSPrefix, 'grid-table',
|
'<tpl if="summaryRecord"> ', Ext.baseCSSPrefix, 'grid-table-summary</tpl>"',
|
'border="0" cellspacing="0" cellpadding="0" style="width:100%">',
|
'{[values.view.renderColumnSizer(out)]}',
|
// Only output the first row if this is *not* a collapsed group
|
'<tpl if="!isCollapsedGroup">',
|
'{%',
|
'values.itemClasses.length = 0;',
|
'this.nextTpl.applyOut(values, out, parent);',
|
'%}',
|
'</tpl>',
|
'<tpl if="summaryRecord">',
|
'{%me.outputSummaryRecord(values.summaryRecord, values, out);%}',
|
'</tpl>',
|
'</table>',
|
'</tpl>',
|
'</td>',
|
'</tr>',
|
'<tpl else>',
|
'{%this.nextTpl.applyOut(values, out, parent);%}',
|
'</tpl>', {
|
priority: 200,
|
|
syncRowHeights: function(firstRow, secondRow) {
|
firstRow = Ext.fly(firstRow, 'syncDest');
|
secondRow = Ext.fly(secondRow, 'sycSrc');
|
var owner = this.owner,
|
firstHd = firstRow.down(owner.eventSelector, true),
|
secondHd,
|
firstSummaryRow = firstRow.down(owner.summaryRowSelector, true),
|
secondSummaryRow,
|
firstHeight, secondHeight;
|
|
// Sync the heights of header elements in each row if they need it.
|
if (firstHd && (secondHd = secondRow.down(owner.eventSelector, true))) {
|
firstHd.style.height = secondHd.style.height = '';
|
if ((firstHeight = firstHd.offsetHeight) > (secondHeight = secondHd.offsetHeight)) {
|
Ext.fly(secondHd).setHeight(firstHeight);
|
}
|
else if (secondHeight > firstHeight) {
|
Ext.fly(firstHd).setHeight(secondHeight);
|
}
|
}
|
|
// Sync the heights of summary row in each row if they need it.
|
if (firstSummaryRow && (secondSummaryRow = secondRow.down(owner.summaryRowSelector, true))) {
|
firstSummaryRow.style.height = secondSummaryRow.style.height = '';
|
if ((firstHeight = firstSummaryRow.offsetHeight) > (secondHeight = secondSummaryRow.offsetHeight)) {
|
Ext.fly(secondSummaryRow).setHeight(firstHeight);
|
}
|
else if (secondHeight > firstHeight) {
|
Ext.fly(firstSummaryRow).setHeight(secondHeight);
|
}
|
}
|
},
|
|
syncContent: function(destRow, sourceRow) {
|
destRow = Ext.fly(destRow, 'syncDest');
|
sourceRow = Ext.fly(sourceRow, 'sycSrc');
|
var owner = this.owner,
|
destHd = destRow.down(owner.eventSelector, true),
|
sourceHd = sourceRow.down(owner.eventSelector, true),
|
destSummaryRow = destRow.down(owner.summaryRowSelector, true),
|
sourceSummaryRow = sourceRow.down(owner.summaryRowSelector, true);
|
|
// Sync the content of header element.
|
if (destHd && sourceHd) {
|
Ext.fly(destHd).syncContent(sourceHd);
|
}
|
|
// Sync the content of summary row element.
|
if (destSummaryRow && sourceSummaryRow) {
|
Ext.fly(destSummaryRow).syncContent(sourceSummaryRow);
|
}
|
}
|
}
|
],
|
|
<span id='Ext-grid-feature-Grouping-method-constructor'> constructor: function() {
|
</span> this.groupCache = {};
|
this.callParent(arguments);
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-init'> init: function(grid) {
|
</span> var me = this,
|
view = me.view;
|
|
view.isGrouping = true;
|
|
// The expensively maintained groupCache is shared between twinned Grouping features.
|
if (me.lockingPartner && me.lockingPartner.groupCache) {
|
me.groupCache = me.lockingPartner.groupCache;
|
}
|
|
me.mixins.summary.init.call(me);
|
|
me.callParent(arguments);
|
view.headerCt.on({
|
columnhide: me.onColumnHideShow,
|
columnshow: me.onColumnHideShow,
|
columnmove: me.onColumnMove,
|
scope: me
|
});
|
|
// Add a table level processor
|
view.addTableTpl(me.tableTpl).groupingFeature = me;
|
|
// Add a row level processor
|
view.addRowTpl(Ext.XTemplate.getTpl(me, 'groupTpl')).groupingFeature = me;
|
|
view.preserveScrollOnRefresh = true;
|
|
// Sparse store - we can never collapse groups
|
if (view.store.buffered) {
|
me.collapsible = false;
|
}
|
// If it's a local store we can build a grouped store for use as the view's dataSource
|
else {
|
|
// Share the GroupStore between both sides of a locked grid
|
if (this.lockingPartner && this.lockingPartner.dataSource) {
|
me.dataSource = view.dataSource = this.lockingPartner.dataSource;
|
} else {
|
me.dataSource = view.dataSource = new Ext.grid.feature.GroupStore(me, view.store);
|
}
|
}
|
|
me.grid.on({
|
reconfigure: me.onReconfigure
|
});
|
view.on({
|
afterrender: me.afterViewRender,
|
scope: me,
|
single: true
|
});
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-clearGroupCache'> clearGroupCache: function() {
|
</span> var me = this,
|
groupCache = me.groupCache = {};
|
|
if (me.lockingPartner) {
|
me.lockingPartner.groupCache = groupCache;
|
}
|
return groupCache;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-vetoEvent'> vetoEvent: function(record, row, rowIndex, e) {
|
</span> // Do not veto mouseover/mouseout
|
if (e.type !== 'mouseover' && e.type !== 'mouseout' && e.type !== 'mouseenter' && e.type !== 'mouseleave' && e.getTarget(this.eventSelector)) {
|
return false;
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-enable'> enable: function() {
|
</span> var me = this,
|
view = me.view,
|
store = view.store,
|
groupToggleMenuItem;
|
|
me.lastGroupField = me.getGroupField();
|
|
view.isGrouping = true;
|
if (me.lastGroupIndex) {
|
me.block();
|
store.group(me.lastGroupIndex);
|
me.unblock();
|
}
|
me.callParent();
|
groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
|
if (groupToggleMenuItem) {
|
groupToggleMenuItem.setChecked(true, true);
|
}
|
me.refreshIf();
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-disable'> disable: function() {
|
</span> var me = this,
|
view = me.view,
|
store = view.store,
|
groupToggleMenuItem,
|
lastGroup;
|
|
view.isGrouping = false;
|
lastGroup = store.groupers.first();
|
if (lastGroup) {
|
me.lastGroupIndex = lastGroup.property;
|
me.block();
|
store.clearGrouping();
|
me.unblock();
|
}
|
|
me.callParent();
|
groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
|
if (groupToggleMenuItem) {
|
groupToggleMenuItem.setChecked(false, true);
|
}
|
me.refreshIf();
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-refreshIf'> refreshIf: function() {
|
</span> var ownerCt = this.grid.ownerCt,
|
view = this.view;
|
|
if (!view.store.remoteGroup && !this.blockRefresh) {
|
|
// We are one side of a lockable grid, so refresh the locking view
|
if (ownerCt && ownerCt.lockable) {
|
ownerCt.view.refresh();
|
} else {
|
view.refresh();
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-afterViewRender'> // Attach events to view
|
</span> afterViewRender: function() {
|
var me = this,
|
view = me.view;
|
|
view.on({
|
scope: me,
|
groupclick: me.onGroupClick
|
});
|
|
if (me.enableGroupingMenu) {
|
me.injectGroupingMenu();
|
}
|
|
me.pruneGroupedHeader();
|
|
me.lastGroupField = me.getGroupField();
|
me.block();
|
me.onGroupChange();
|
me.unblock();
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-injectGroupingMenu'> injectGroupingMenu: function() {
|
</span> var me = this,
|
headerCt = me.view.headerCt;
|
|
headerCt.showMenuBy = me.showMenuBy;
|
headerCt.getMenuItems = me.getMenuItems();
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onColumnHideShow'> onColumnHideShow: function(headerOwnerCt, header) {
|
</span> var view = this.view,
|
headerCt = view.headerCt,
|
menu = headerCt.getMenu(),
|
groupToggleMenuItem = menu.down('#groupMenuItem'),
|
colCount = headerCt.getGridColumns().length,
|
items,
|
len,
|
i;
|
|
// "Group by this field" must be disabled if there's only one column left visible.
|
if (groupToggleMenuItem) {
|
if (headerCt.getVisibleGridColumns().length > 1) {
|
groupToggleMenuItem.enable();
|
} else {
|
groupToggleMenuItem.disable();
|
}
|
}
|
|
// header containing TDs have to span all columns, hiddens are just zero width
|
if (view.rendered) {
|
items = view.el.query('.' + this.ctCls);
|
for (i = 0, len = items.length; i < len; ++i) {
|
items[i].colSpan = colCount;
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onColumnMove'> // Update first and last records in groups when column moves
|
</span> // Because of the RowWrap template, this will update the groups' headers and footers
|
onColumnMove: function() {
|
var me = this,
|
store = me.view.store,
|
groups,
|
i, len,
|
groupInfo, firstRec, lastRec;
|
|
if (store.isGrouped()) {
|
groups = store.getGroups();
|
len = groups.length;
|
|
// Iterate through groups, firing updates on boundary records
|
for (i = 0; i < len; i++) {
|
groupInfo = groups[i];
|
firstRec = groupInfo.children[0];
|
lastRec = groupInfo.children[groupInfo.children.length - 1];
|
|
// Must pass the modifiedFields parameter as null so that the
|
// listener options does not take that place in the arguments list
|
store.fireEvent('update', store, firstRec, 'edit', null);
|
if (lastRec !== firstRec) {
|
store.fireEvent('update', store, lastRec, 'edit', null);
|
}
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-showMenuBy'> showMenuBy: function(t, header) {
|
</span> var menu = this.getMenu(),
|
groupMenuItem = menu.down('#groupMenuItem'),
|
groupMenuMeth = header.groupable === false || this.view.headerCt.getVisibleGridColumns().length < 2 ? 'disable' : 'enable',
|
groupToggleMenuItem = menu.down('#groupToggleMenuItem'),
|
isGrouped = this.view.store.isGrouped();
|
|
groupMenuItem[groupMenuMeth]();
|
if (groupToggleMenuItem) {
|
groupToggleMenuItem.setChecked(isGrouped, true);
|
groupToggleMenuItem[isGrouped ? 'enable' : 'disable']();
|
}
|
Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getMenuItems'> getMenuItems: function() {
|
</span> var me = this,
|
groupByText = me.groupByText,
|
disabled = me.disabled || !me.getGroupField(),
|
showGroupsText = me.showGroupsText,
|
enableNoGroups = me.enableNoGroups,
|
getMenuItems = me.view.headerCt.getMenuItems;
|
|
// runs in the scope of headerCt
|
return function() {
|
|
// We cannot use the method from HeaderContainer's prototype here
|
// because other plugins or features may already have injected an implementation
|
var o = getMenuItems.call(this);
|
o.push('-', {
|
iconCls: Ext.baseCSSPrefix + 'group-by-icon',
|
itemId: 'groupMenuItem',
|
text: groupByText,
|
handler: me.onGroupMenuItemClick,
|
scope: me
|
});
|
if (enableNoGroups) {
|
o.push({
|
itemId: 'groupToggleMenuItem',
|
text: showGroupsText,
|
checked: !disabled,
|
checkHandler: me.onGroupToggleMenuItemClick,
|
scope: me
|
});
|
}
|
return o;
|
};
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onGroupMenuItemClick'> /**
|
</span> * Group by the header the user has clicked on.
|
* @private
|
*/
|
onGroupMenuItemClick: function(menuItem, e) {
|
var me = this,
|
menu = menuItem.parentMenu,
|
hdr = menu.activeHeader,
|
view = me.view,
|
store = view.store;
|
|
me.lastGroupIndex = null;
|
me.block();
|
me.enable();
|
store.group(hdr.dataIndex);
|
me.pruneGroupedHeader();
|
me.unblock();
|
me.refreshIf();
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-block'> block: function(fromPartner) {
|
</span> this.blockRefresh = this.view.blockRefresh = true;
|
if (this.lockingPartner && !fromPartner) {
|
this.lockingPartner.block(true);
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-unblock'> unblock: function(fromPartner) {
|
</span> this.blockRefresh = this.view.blockRefresh = false;
|
if (this.lockingPartner && !fromPartner) {
|
this.lockingPartner.unblock(true);
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onGroupToggleMenuItemClick'> /**
|
</span> * Turn on and off grouping via the menu
|
* @private
|
*/
|
onGroupToggleMenuItemClick: function(menuItem, checked) {
|
this[checked ? 'enable' : 'disable']();
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-pruneGroupedHeader'> /**
|
</span> * Prunes the grouped header from the header container
|
* @private
|
*/
|
pruneGroupedHeader: function() {
|
var me = this,
|
header = me.getGroupedHeader();
|
|
if (me.hideGroupedHeader && header) {
|
Ext.suspendLayouts();
|
if (me.prunedHeader && me.prunedHeader !== header) {
|
me.prunedHeader.show();
|
}
|
me.prunedHeader = header;
|
header.hide();
|
Ext.resumeLayouts(true);
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getHeaderNode'> getHeaderNode: function(groupName) {
|
</span> return Ext.get(this.createGroupId(groupName));
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getGroup'> getGroup: function(name) {
|
</span> var cache = this.groupCache,
|
item = cache[name];
|
|
if (!item) {
|
item = cache[name] = {
|
isCollapsed: false
|
};
|
}
|
return item;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-isExpanded'> /**
|
</span> * Returns `true` if the named group is expanded.
|
* @param {String} groupName The group name as returned from {@link Ext.data.Store#getGroupString getGroupString}. This is usually the value of
|
* the {@link Ext.data.Store#groupField groupField}.
|
* @return {Boolean} `true` if the group defined by that value is expanded.
|
*/
|
isExpanded: function(groupName) {
|
return !this.getGroup(groupName).isCollapsed;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-expand'> /**
|
</span> * Expand a group
|
* @param {String} groupName The group name
|
* @param {Boolean} focus Pass `true` to focus the group after expand.
|
*/
|
expand: function(groupName, focus) {
|
this.doCollapseExpand(false, groupName, focus);
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-expandAll'> /**
|
</span> * Expand all groups
|
*/
|
expandAll: function() {
|
var me = this,
|
view = me.view,
|
groupCache = me.groupCache,
|
groupName,
|
lockingPartner = me.lockingPartner,
|
partnerView;
|
|
// Clear all collapsed flags.
|
// groupCache is shared between two lockingPartners
|
for (groupName in groupCache) {
|
if (groupCache.hasOwnProperty(groupName)) {
|
groupCache[groupName].isCollapsed = false;
|
}
|
}
|
Ext.suspendLayouts();
|
view.suspendEvent('beforerefresh', 'refresh');
|
if (lockingPartner) {
|
partnerView = lockingPartner.view
|
partnerView.suspendEvent('beforerefresh', 'refresh');
|
}
|
me.dataSource.onRefresh();
|
view.resumeEvent('beforerefresh', 'refresh');
|
if (lockingPartner) {
|
partnerView.resumeEvent('beforerefresh', 'refresh');
|
}
|
Ext.resumeLayouts(true);
|
|
// Fire event for all groups post expand
|
for (groupName in groupCache) {
|
if (groupCache.hasOwnProperty(groupName)) {
|
me.afterCollapseExpand(false, groupName);
|
if (lockingPartner) {
|
lockingPartner.afterCollapseExpand(false, groupName);
|
}
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-collapse'> /**
|
</span> * Collapse a group
|
* @param {String} groupName The group name
|
* @param {Boolean} focus Pass `true` to focus the group after expand.
|
*/
|
collapse: function(groupName, focus) {
|
this.doCollapseExpand(true, groupName, focus);
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-isAllCollapsed'> // private
|
</span> // Returns true if all groups are collapsed
|
isAllCollapsed: function() {
|
var me = this,
|
groupCache = me.groupCache,
|
groupName;
|
|
// Clear all collapsed flags.
|
// groupCache is shared between two lockingPartners
|
for (groupName in groupCache) {
|
if (groupCache.hasOwnProperty(groupName)) {
|
if (!groupCache[groupName].isCollapsed) {
|
return false;
|
}
|
}
|
}
|
return true;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-isAllExpanded'> // private
|
</span> // Returns true if all groups are expanded
|
isAllExpanded: function() {
|
var me = this,
|
groupCache = me.groupCache,
|
groupName;
|
|
// Clear all collapsed flags.
|
// groupCache is shared between two lockingPartners
|
for (groupName in groupCache) {
|
if (groupCache.hasOwnProperty(groupName)) {
|
if (groupCache[groupName].isCollapsed) {
|
return false;
|
}
|
}
|
}
|
return true;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-collapseAll'> /**
|
</span> * Collapse all groups
|
*/
|
collapseAll: function() {
|
var me = this,
|
view = me.view,
|
groupCache = me.groupCache,
|
groupName,
|
lockingPartner = me.lockingPartner,
|
partnerView;
|
|
// Set all collapsed flags
|
// groupCache is shared between two lockingPartners
|
for (groupName in groupCache) {
|
if (groupCache.hasOwnProperty(groupName)) {
|
groupCache[groupName].isCollapsed = true;
|
}
|
}
|
Ext.suspendLayouts();
|
view.suspendEvent('beforerefresh', 'refresh');
|
if (lockingPartner) {
|
partnerView = lockingPartner.view
|
partnerView.suspendEvent('beforerefresh', 'refresh');
|
}
|
me.dataSource.onRefresh();
|
view.resumeEvent('beforerefresh', 'refresh');
|
if (lockingPartner) {
|
partnerView.resumeEvent('beforerefresh', 'refresh');
|
}
|
|
if (lockingPartner && !lockingPartner.isAllCollapsed()) {
|
lockingPartner.collapseAll();
|
}
|
Ext.resumeLayouts(true);
|
|
// Fire event for all groups post collapse
|
for (groupName in groupCache) {
|
if (groupCache.hasOwnProperty(groupName)) {
|
me.afterCollapseExpand(true, groupName);
|
if (lockingPartner) {
|
lockingPartner.afterCollapseExpand(true, groupName);
|
}
|
}
|
}
|
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-doCollapseExpand'> doCollapseExpand: function(collapsed, groupName, focus) {
|
</span> var me = this,
|
lockingPartner = me.lockingPartner,
|
group = me.groupCache[groupName];
|
|
// groupCache is shared between two lockingPartners
|
if (group.isCollapsed != collapsed) {
|
|
// The GroupStore is shared by partnered Grouping features, so this will refresh both sides.
|
// We only want one layout as a result though, so suspend layouts while refreshing.
|
Ext.suspendLayouts();
|
if (collapsed) {
|
me.dataSource.collapseGroup(group);
|
} else {
|
me.dataSource.expandGroup(group);
|
}
|
Ext.resumeLayouts(true);
|
|
// Sync the group state and focus the row if requested.
|
me.afterCollapseExpand(collapsed, groupName, focus);
|
|
// Sync the lockingPartner's group state.
|
// Do not pass on focus flag. If we were told to focus, we must focus, not the other side.
|
if (lockingPartner) {
|
lockingPartner.afterCollapseExpand(collapsed, groupName, false);
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-afterCollapseExpand'> afterCollapseExpand: function(collapsed, groupName, focus) {
|
</span> var me = this,
|
view = me.view,
|
header;
|
|
header = Ext.get(this.getHeaderNode(groupName));
|
view.fireEvent(collapsed ? 'groupcollapse' : 'groupexpand', view, header, groupName);
|
if (focus) {
|
header.up(view.getItemSelector()).scrollIntoView(view.el, null, true);
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onGroupChange'> onGroupChange: function() {
|
</span> var me = this,
|
field = me.getGroupField(),
|
menuItem,
|
visibleGridColumns,
|
groupingByLastVisibleColumn;
|
|
if (me.hideGroupedHeader) {
|
if (me.lastGroupField) {
|
menuItem = me.getMenuItem(me.lastGroupField);
|
if (menuItem) {
|
menuItem.setChecked(true);
|
}
|
}
|
if (field) {
|
visibleGridColumns = me.view.headerCt.getVisibleGridColumns();
|
|
// See if we are being asked to group by the sole remaining visible column.
|
// If so, then do not hide that column.
|
groupingByLastVisibleColumn = ((visibleGridColumns.length === 1) && (visibleGridColumns[0].dataIndex == field));
|
menuItem = me.getMenuItem(field);
|
if (menuItem && !groupingByLastVisibleColumn) {
|
menuItem.setChecked(false);
|
}
|
}
|
}
|
me.refreshIf();
|
me.lastGroupField = field;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getMenuItem'> /**
|
</span> * Gets the related menu item for a dataIndex
|
* @private
|
* @return {Ext.grid.header.Container} The header
|
*/
|
getMenuItem: function(dataIndex){
|
var view = this.view,
|
header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
|
menu = view.headerCt.getMenu();
|
|
return header ? menu.down('menuitem[headerId='+ header.id +']') : null;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onGroupKey'> onGroupKey: function(keyCode, event) {
|
</span> var me = this,
|
groupName = me.getGroupName(event.target);
|
|
if (groupName) {
|
me.onGroupClick(me.view, event.target, groupName, event);
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onGroupClick'> /**
|
</span> * Toggle between expanded/collapsed state when clicking on
|
* the group.
|
* @private
|
*/
|
onGroupClick: function(view, rowElement, groupName, e) {
|
var me = this,
|
groupCache = me.groupCache,
|
groupIsCollapsed = !me.isExpanded(groupName),
|
g;
|
|
if (me.collapsible) {
|
|
// CTRL means collapse all others
|
if (e.ctrlKey) {
|
Ext.suspendLayouts();
|
for (g in groupCache) {
|
if (g === groupName) {
|
if (groupIsCollapsed) {
|
me.expand(groupName);
|
}
|
} else {
|
me.doCollapseExpand(true, g, false);
|
}
|
}
|
Ext.resumeLayouts(true);
|
return;
|
}
|
|
if (groupIsCollapsed) {
|
me.expand(groupName);
|
} else {
|
me.collapse(groupName);
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-setupRowData'> setupRowData: function(record, idx, rowValues) {
|
</span> var me = this,
|
data = me.refreshData,
|
groupInfo = me.groupInfo,
|
header = data.header,
|
groupField = data.groupField,
|
store = me.view.dataSource,
|
grouper, groupName, prev, next;
|
|
rowValues.isCollapsedGroup = false;
|
rowValues.summaryRecord = null;
|
|
if (data.doGrouping) {
|
grouper = me.view.store.groupers.first();
|
|
// This is a placeholder record which represents a whole collapsed group
|
// It is a special case.
|
if (record.children) {
|
groupName = grouper.getGroupString(record.children[0]);
|
|
rowValues.isFirstRow = rowValues.isLastRow = true;
|
rowValues.itemClasses.push(me.hdCollapsedCls);
|
rowValues.isCollapsedGroup = true;
|
rowValues.groupInfo = groupInfo;
|
groupInfo.groupField = groupField;
|
groupInfo.name = groupName;
|
groupInfo.groupValue = record.children[0].get(groupField);
|
groupInfo.columnName = header ? header.text : groupField;
|
rowValues.collapsibleCls = me.collapsible ? me.collapsibleCls : me.hdNotCollapsibleCls;
|
rowValues.groupId = me.createGroupId(groupName);
|
groupInfo.rows = groupInfo.children = record.children;
|
if (me.showSummaryRow) {
|
rowValues.summaryRecord = data.summaryData[groupName];
|
}
|
return;
|
}
|
|
groupName = grouper.getGroupString(record);
|
|
// See if the current record is the last in the group
|
rowValues.isFirstRow = idx === 0;
|
if (!rowValues.isFirstRow) {
|
prev = store.getAt(idx - 1);
|
// If the previous row is of a different group, then we're at the first for a new group
|
if (prev) {
|
// Must use Model's comparison because Date objects are never equal
|
rowValues.isFirstRow = !prev.isEqual(grouper.getGroupString(prev), groupName);
|
}
|
}
|
|
// See if the current record is the last in the group
|
rowValues.isLastRow = idx == store.getTotalCount() - 1;
|
if (!rowValues.isLastRow) {
|
next = store.getAt(idx + 1);
|
if (next) {
|
// Must use Model's comparison because Date objects are never equal
|
rowValues.isLastRow = !next.isEqual(grouper.getGroupString(next), groupName);
|
}
|
}
|
|
if (rowValues.isFirstRow) {
|
groupInfo.groupField = groupField;
|
groupInfo.name = groupName;
|
groupInfo.groupValue = record.get(groupField);
|
groupInfo.columnName = header ? header.text : groupField;
|
rowValues.collapsibleCls = me.collapsible ? me.collapsibleCls : me.hdNotCollapsibleCls;
|
rowValues.groupId = me.createGroupId(groupName);
|
|
if (!me.isExpanded(groupName)) {
|
rowValues.itemClasses.push(me.hdCollapsedCls);
|
rowValues.isCollapsedGroup = true;
|
}
|
|
// We only get passed a GroupStore if the store is not buffered
|
if (store.buffered) {
|
groupInfo.rows = groupInfo.children = [];
|
} else {
|
groupInfo.rows = groupInfo.children = me.getRecordGroup(record).children;
|
}
|
rowValues.groupInfo = groupInfo;
|
}
|
|
if (rowValues.isLastRow) {
|
// Add the group's summary record to the last record in the group
|
if (me.showSummaryRow) {
|
rowValues.summaryRecord = data.summaryData[groupName];
|
}
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-setup'> setup: function(rows, rowValues) {
|
</span> var me = this,
|
data = me.refreshData,
|
isGrouping = !me.disabled && me.view.store.isGrouped();
|
|
me.skippedRows = 0;
|
if (rowValues.view.bufferedRenderer) {
|
rowValues.view.bufferedRenderer.variableRowHeight = true;
|
}
|
data.groupField = me.getGroupField();
|
data.header = me.getGroupedHeader(data.groupField);
|
data.doGrouping = isGrouping;
|
rowValues.groupHeaderTpl = Ext.XTemplate.getTpl(me, 'groupHeaderTpl');
|
|
if (isGrouping && me.showSummaryRow) {
|
data.summaryData = me.generateSummaryData();
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-cleanup'> cleanup: function(rows, rowValues) {
|
</span> var data = this.refreshData;
|
|
rowValues.groupInfo = rowValues.groupHeaderTpl = rowValues.isFirstRow = null;
|
data.groupField = data.header = null;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getGroupName'> getGroupName: function(element) {
|
</span> var me = this,
|
view = me.view,
|
eventSelector = me.eventSelector,
|
parts,
|
targetEl,
|
row;
|
|
// See if element is, or is within a group header. If so, we can extract its name
|
targetEl = Ext.fly(element).findParent(eventSelector);
|
|
if (!targetEl) {
|
// Otherwise, navigate up to the row and look down to see if we can find it
|
row = Ext.fly(element).findParent(view.itemSelector);
|
if (row) {
|
targetEl = row.down(eventSelector, true);
|
}
|
}
|
|
if (targetEl) {
|
parts = targetEl.id.split(view.id + '-hd-');
|
if (parts.length === 2) {
|
return Ext.htmlDecode(parts[1]);
|
}
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getRecordGroup'> /**
|
</span> * Returns the group data object for the group to which the passed record belongs **if the Store is grouped**.
|
*
|
* @param {Ext.data.Model} record The record for which to return group information.
|
* @return {Object} A single group data block as returned from {@link Ext.data.Store#getGroups Store.getGroups}. Returns
|
* `undefined` if the Store is not grouped.
|
*
|
*/
|
getRecordGroup: function(record) {
|
var grouper = this.view.store.groupers.first();
|
if (grouper) {
|
return this.groupCache[grouper.getGroupString(record)];
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-createGroupId'> createGroupId: function(group) {
|
</span> return this.view.id + '-hd-' + Ext.htmlEncode(group);
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-createGroupCls'> createGroupCls: function(group) {
|
</span> return this.view.id + '-' + Ext.htmlEncode(group) + '-item';
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getGroupField'> getGroupField: function(){
|
</span> return this.view.store.getGroupField();
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getGroupedHeader'> getGroupedHeader: function(groupField) {
|
</span> var me = this,
|
headerCt = me.view.headerCt,
|
partner = me.lockingPartner,
|
selector, header;
|
|
groupField = groupField || this.getGroupField();
|
|
if (groupField) {
|
selector = '[dataIndex=' + groupField + ']';
|
header = headerCt.down(selector);
|
// The header may exist in the locking partner, so check there as well
|
if (!header && partner) {
|
header = partner.view.headerCt.down(selector);
|
}
|
}
|
return header || null;
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-getFireEventArgs'> getFireEventArgs: function(type, view, targetEl, e) {
|
</span> return [type, view, targetEl, this.getGroupName(targetEl), e];
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-destroy'> destroy: function(){
|
</span> var me = this,
|
dataSource = me.dataSource;
|
|
me.view = me.prunedHeader = me.grid = me.groupCache = me.dataSource = null;
|
me.callParent();
|
if (dataSource) {
|
dataSource.bindStore(null);
|
}
|
},
|
|
<span id='Ext-grid-feature-Grouping-method-onReconfigure'> onReconfigure: function(grid, store, columns, oldStore, oldColumns) {
|
</span> var me = grid;
|
|
if (store && store !== oldStore) {
|
// Grouping involves injecting a dataSource in early
|
if (store.buffered !== oldStore.buffered) {
|
Ext.Error.raise('Cannot reconfigure grouping switching between buffered and non-buffered stores');
|
}
|
if (store.buffered) {
|
me.bindStore(store);
|
me.dataSource.processStore(store);
|
}
|
}
|
}
|
});</pre>
|
</body>
|
</html>
|