--- /dev/null
+<!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-grid.feature.Grouping'>/**
+</span> * @class Ext.grid.feature.Grouping
+ * @extends Ext.grid.feature.Feature
+ *
+ * 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 thew 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
+ *
+ * var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
+ * groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
+ * startCollapsed: true // start all groups collapsed
+ * });
+ *
+ * @ftype grouping
+ * @author Nicolas Ferrero
+ */
+Ext.define('Ext.grid.feature.Grouping', {
+ extend: 'Ext.grid.feature.Feature',
+ alias: 'feature.grouping',
+
+ eventPrefix: 'group',
+ eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
+
+ constructor: function() {
+ this.collapsedState = {};
+ this.callParent(arguments);
+ },
+
+<span id='Ext-grid.feature.Grouping-event-groupclick'> /**
+</span> * @event groupclick
+ * @param {Ext.view.Table} view
+ * @param {HTMLElement} node
+ * @param {Number} unused
+ * @param {Number} unused
+ * @param {Ext.EventObject} e
+ */
+
+<span id='Ext-grid.feature.Grouping-event-groupdblclick'> /**
+</span> * @event groupdblclick
+ * @param {Ext.view.Table} view
+ * @param {HTMLElement} node
+ * @param {Number} unused
+ * @param {Number} unused
+ * @param {Ext.EventObject} e
+ */
+
+<span id='Ext-grid.feature.Grouping-event-groupcontextmenu'> /**
+</span> * @event groupcontextmenu
+ * @param {Ext.view.Table} view
+ * @param {HTMLElement} node
+ * @param {Number} unused
+ * @param {Number} unused
+ * @param {Ext.EventObject} e
+ */
+
+<span id='Ext-grid.feature.Grouping-event-groupcollapse'> /**
+</span> * @event groupcollapse
+ * @param {Ext.view.Table} view
+ * @param {HTMLElement} node
+ * @param {Number} unused
+ * @param {Number} unused
+ * @param {Ext.EventObject} e
+ */
+
+<span id='Ext-grid.feature.Grouping-event-groupexpand'> /**
+</span> * @event groupexpand
+ * @param {Ext.view.Table} view
+ * @param {HTMLElement} node
+ * @param {Number} unused
+ * @param {Number} unused
+ * @param {Ext.EventObject} e
+ */
+
+<span id='Ext-grid.feature.Grouping-cfg-groupHeaderTpl'> /**
+</span> * @cfg {String} groupHeaderTpl
+ * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
+ * Defaults to 'Group: {name}'
+ */
+ groupHeaderTpl: 'Group: {name}',
+
+<span id='Ext-grid.feature.Grouping-cfg-depthToIndent'> /**
+</span> * @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',
+
+<span id='Ext-grid.feature.Grouping-cfg-groupByText'> /**
+</span> * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header
+ * (defaults to 'Group By This Field').
+ */
+ groupByText : 'Group By This Field',
+<span id='Ext-grid.feature.Grouping-cfg-showGroupsText'> /**
+</span> * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
+ * (defaults to 'Show in Groups').
+ */
+ showGroupsText : 'Show in Groups',
+
+<span id='Ext-grid.feature.Grouping-cfg-hideGroupedHeader'> /**
+</span> * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped (defaults to <tt>false</tt>)
+ */
+ hideGroupedHeader : false,
+
+<span id='Ext-grid.feature.Grouping-cfg-startCollapsed'> /**
+</span> * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed (defaults to <tt>false</tt>)
+ */
+ startCollapsed : false,
+
+<span id='Ext-grid.feature.Grouping-cfg-enableGroupingMenu'> /**
+</span> * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu (defaults to <tt>true</tt>)
+ */
+ enableGroupingMenu : true,
+
+<span id='Ext-grid.feature.Grouping-cfg-enableNoGroups'> /**
+</span> * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping (defaults to <tt>true</tt>)
+ */
+ enableNoGroups : true,
+
+ enable: function() {
+ var me = this,
+ view = me.view,
+ store = view.store,
+ groupToggleMenuItem;
+
+ if (me.lastGroupIndex) {
+ store.group(me.lastGroupIndex);
+ }
+ me.callParent();
+ groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
+ groupToggleMenuItem.setChecked(true, true);
+ view.refresh();
+ },
+
+ disable: function() {
+ var me = this,
+ view = me.view,
+ store = view.store,
+ groupToggleMenuItem,
+ lastGroup;
+
+ lastGroup = store.groupers.first();
+ if (lastGroup) {
+ me.lastGroupIndex = lastGroup.property;
+ store.groupers.clear();
+ }
+
+ me.callParent();
+ groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
+ groupToggleMenuItem.setChecked(true, true);
+ groupToggleMenuItem.setChecked(false, true);
+ view.refresh();
+ },
+
+ getFeatureTpl: function(values, parent, x, xcount) {
+ var me = this;
+
+ return [
+ '<tpl if="typeof rows !== \'undefined\'">',
+ // group row tpl
+ '<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>',
+ // this is the rowbody
+ '<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>',
+ '</tpl>'
+ ].join('');
+ },
+
+ getFragmentTpl: 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() {
+ var me = this;
+
+ delete me.view;
+ delete me.prunedHeader;
+ },
+
+ // perhaps rename to afterViewRender
+ attachEvents: function() {
+ var me = this,
+ view = me.view,
+ header, headerId, menu, menuItem;
+
+ view.on({
+ scope: me,
+ groupclick: me.onGroupClick,
+ rowfocus: me.onRowFocus
+ });
+ view.store.on('groupchange', me.onGroupChange, me);
+
+ me.pruneGroupedHeader();
+
+ if (me.enableGroupingMenu) {
+ me.injectGroupingMenu();
+ }
+
+ if (me.hideGroupedHeader) {
+ header = view.headerCt.down('gridcolumn[dataIndex=' + me.getGroupField() + ']');
+ headerId = header.id;
+ menu = view.headerCt.getMenu();
+ menuItem = menu.down('menuitem[headerId='+ headerId +']');
+ if (menuItem) {
+ menuItem.setChecked(false);
+ }
+ }
+ },
+
+ injectGroupingMenu: function() {
+ var me = this,
+ view = me.view,
+ headerCt = view.headerCt;
+ headerCt.showMenuBy = me.showMenuBy;
+ headerCt.getMenuItems = me.getMenuItems();
+ },
+
+ showMenuBy: function(t, header) {
+ var menu = this.getMenu(),
+ groupMenuItem = menu.down('#groupMenuItem'),
+ groupableMth = header.groupable === false ? 'disable' : 'enable';
+
+ groupMenuItem[groupableMth]();
+ Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
+ },
+
+ getMenuItems: function() {
+ var me = this,
+ groupByText = me.groupByText,
+ disabled = me.disabled,
+ showGroupsText = me.showGroupsText,
+ enableNoGroups = me.enableNoGroups,
+ groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
+ groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me)
+
+ // runs in the scope of headerCt
+ return function() {
+ var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
+ o.push('-', {
+ itemId: 'groupMenuItem',
+ text: groupByText,
+ handler: groupMenuItemClick
+ });
+ if (enableNoGroups) {
+ o.push({
+ itemId: 'groupToggleMenuItem',
+ text: showGroupsText,
+ checked: !disabled,
+ checkHandler: groupToggleMenuItemClick
+ });
+ }
+ 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 menu = menuItem.parentMenu,
+ hdr = menu.activeHeader,
+ view = this.view;
+
+ delete this.lastGroupIndex;
+ this.enable();
+ view.store.group(hdr.dataIndex);
+ this.pruneGroupedHeader();
+
+ },
+
+<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,
+ view = me.view,
+ store = view.store,
+ groupField = me.getGroupField(),
+ headerCt = view.headerCt,
+ header = headerCt.down('header[dataIndex=' + groupField + ']');
+
+ if (header) {
+ if (me.prunedHeader) {
+ me.prunedHeader.show();
+ }
+ me.prunedHeader = header;
+ header.hide();
+ }
+ },
+
+ getGroupField: function(){
+ var group = this.view.store.groupers.first();
+ if (group) {
+ return group.property;
+ }
+ return '';
+ },
+
+<span id='Ext-grid.feature.Grouping-method-onRowFocus'> /**
+</span> * 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);
+ }
+ },
+
+<span id='Ext-grid.feature.Grouping-method-expand'> /**
+</span> * Expand a group by the groupBody
+ * @param {Ext.core.Element} groupBd
+ * @private
+ */
+ expand: function(groupBd) {
+ var me = this,
+ view = me.view,
+ grid = view.up('gridpanel'),
+ groupBdDom = Ext.getDom(groupBd);
+
+ me.collapsedState[groupBdDom.id] = false;
+
+ groupBd.removeCls(me.collapsedCls);
+ groupBd.prev().removeCls(me.hdCollapsedCls);
+
+ grid.determineScrollbars();
+ grid.invalidateScroller();
+ view.fireEvent('groupexpand');
+ },
+
+<span id='Ext-grid.feature.Grouping-method-collapse'> /**
+</span> * Collapse a group by the groupBody
+ * @param {Ext.core.Element} groupBd
+ * @private
+ */
+ collapse: function(groupBd) {
+ var me = this,
+ view = me.view,
+ grid = view.up('gridpanel'),
+ groupBdDom = Ext.getDom(groupBd);
+
+ me.collapsedState[groupBdDom.id] = true;
+
+ groupBd.addCls(me.collapsedCls);
+ groupBd.prev().addCls(me.hdCollapsedCls);
+
+ grid.determineScrollbars();
+ grid.invalidateScroller();
+ view.fireEvent('groupcollapse');
+ },
+
+ onGroupChange: function(){
+ this.view.refresh();
+ },
+
+<span id='Ext-grid.feature.Grouping-method-onGroupClick'> /**
+</span> * Toggle between expanded/collapsed state when clicking on
+ * the group.
+ * @private
+ */
+ onGroupClick: function(view, group, idx, foo, e) {
+ var me = this,
+ toggleCls = me.toggleCls,
+ groupBd = Ext.fly(group.nextSibling, '_grouping');
+
+ if (groupBd.hasCls(me.collapsedCls)) {
+ me.expand(groupBd);
+ } else {
+ me.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 '<tpl if="typeof rows === \'undefined\'">';
+ },
+
+ // injected into rowtpl and wrapped around metaRowTpl
+ // becomes part of the standard tpl
+ closeRow: function() {
+ return '</tpl>';
+ },
+
+ // 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] : '');
+ o.collapsed = 'true';
+ return o;
+ },
+
+ // return matching preppedRecords
+ getGroupRows: function(group, records, preppedRecords, fullWidth) {
+ var me = this,
+ children = group.children,
+ rows = group.rows = [],
+ view = me.view;
+ group.viewId = view.id;
+
+ Ext.Array.each(records, function(record, idx) {
+ if (Ext.Array.indexOf(children, record) != -1) {
+ rows.push(Ext.apply(preppedRecords[idx], {
+ depth: 1
+ }));
+ }
+ });
+ delete group.children;
+ group.fullWidth = fullWidth;
+ if (me.collapsedState[view.id + '-gp-' + group.name]) {
+ group.collapsedCls = me.collapsedCls;
+ group.hdCollapsedCls = me.hdCollapsedCls;
+ }
+
+ return group;
+ },
+
+ // return the data in a grouped format.
+ collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
+ var me = this,
+ store = me.view.store,
+ groups;
+
+ if (!me.disabled && store.isGrouped()) {
+ groups = store.getGroups();
+ Ext.Array.each(groups, function(group, idx){
+ me.getGroupRows(group, records, preppedRecords, fullWidth);
+ }, me);
+ return {
+ rows: groups,
+ fullWidth: fullWidth
+ };
+ }
+ return o;
+ },
+
+ // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
+ // events that are fired on the view. Chose not to return the actual
+ // group itself because of its expense and because developers can simply
+ // grab the group via store.getGroups(groupName)
+ getFireEventArgs: function(type, view, featureTarget) {
+ var returnArray = [type, view, featureTarget],
+ groupBd = Ext.fly(featureTarget.nextSibling, '_grouping'),
+ groupBdId = Ext.getDom(groupBd).id,
+ prefix = view.id + '-gp-',
+ groupName = groupBdId.substr(prefix.length);
+
+ returnArray.push(groupName);
+
+ return returnArray;
+ }
+});
+</pre></pre></body></html>
\ No newline at end of file