X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/docs/source/Grouping.html diff --git a/docs/source/Grouping.html b/docs/source/Grouping.html new file mode 100644 index 00000000..2368eb66 --- /dev/null +++ b/docs/source/Grouping.html @@ -0,0 +1,519 @@ +Sencha Documentation Project
/**
+ * @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);
+    },
+    
+    /**
+     * @event groupclick
+     * @param {Ext.view.Table} view
+     * @param {HTMLElement} node
+     * @param {Number} unused
+     * @param {Number} unused
+     * @param {Ext.EventObject} e
+     */
+
+    /**
+     * @event groupdblclick
+     * @param {Ext.view.Table} view
+     * @param {HTMLElement} node
+     * @param {Number} unused
+     * @param {Number} unused
+     * @param {Ext.EventObject} e
+     */
+
+    /**
+     * @event groupcontextmenu
+     * @param {Ext.view.Table} view
+     * @param {HTMLElement} node
+     * @param {Number} unused
+     * @param {Number} unused
+     * @param {Ext.EventObject} e
+     */
+
+    /**
+     * @event groupcollapse
+     * @param {Ext.view.Table} view
+     * @param {HTMLElement} node
+     * @param {Number} unused
+     * @param {Number} unused
+     * @param {Ext.EventObject} e
+     */
+
+    /**
+     * @event groupexpand
+     * @param {Ext.view.Table} view
+     * @param {HTMLElement} node
+     * @param {Number} unused
+     * @param {Number} unused
+     * @param {Ext.EventObject} e
+     */
+
+    /**
+     * @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}',
+
+    /**
+     * @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 header
+     * (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} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped (defaults to <tt>false</tt>)
+     */
+    hideGroupedHeader : false,
+
+    /**
+     * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed (defaults to <tt>false</tt>)
+     */
+    startCollapsed : false,
+
+    /**
+     * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu (defaults to <tt>true</tt>)
+     */
+    enableGroupingMenu : true,
+
+    /**
+     * @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;
+        };
+    },
+
+
+    /**
+     * 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();
+        
+    },
+
+    /**
+     * Turn on and off grouping via the menu
+     * @private
+     */
+    onGroupToggleMenuItemClick: function(menuItem, checked) {
+        this[checked ? 'enable' : 'disable']();
+    },
+
+    /**
+     * 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 ''; 
+    },
+
+    /**
+     * 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.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');
+    },
+
+    /**
+     * 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();
+    },
+
+    /**
+     * 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;
+    }
+});
+
\ No newline at end of file