1. #1
    Ext JS Premium Member
    Join Date
    May 2010
    Posts
    14
    Vote Rating
    0
    bpratt65 is on a distinguished road

      0  

    Default Checkboxes on group header rows on a group grid

    I am trying to add a checkbox to each group header row in group grid.

    Has anyone ported over Ext.ux.grid.plugins.GroupCheckboxSelection to ExtJS 4?

    ... or do you simply manipulate the groupHeaderTpl of the Ext.grid.feature.Grouping in the new ExtJS 4 paradigm?

    thanks!

  2. #2
    Ext JS Premium Member
    Join Date
    Apr 2010
    Location
    Sandy, UTah
    Posts
    4
    Vote Rating
    0
    bain4111 is on a distinguished road

      0  

    Default

    Alright, I've worked with bpratt65 to get an Extjs 4 solution to this issue.

    Here is what we came up with.

    We used the general Grouping example and modified only its description

    GroupGrid.html:
    Code:
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Grouped Checkbox Selection Example</title>
    
        <link rel="stylesheet" type="text/css" href="ext-4.0.2/resources/css/ext-all.css" />
        <link rel="stylesheet" type="text/css" href="example.css" />
        <script type="text/javascript" src="ext-4.0.2/bootstrap.js"></script>
        
        <!-- page specific -->
         <script type="text/javascript" src="groupgridexample.js"></script>
        <script type="text/javascript" src="checkGrouping.js"></script>
        <style type="text/css">
            .icon-grid {
                background-image:url(grid.png) !important;
            }
            .icon-clear-group {
                background-image:url(control_rewind.png) !important;
            }
        </style>
    
    </head>
    <body>
        <h1>Group Checkbox Selection Example</h1>
        <p>This example illustrates how to use the grouping feature of the Grid with Checkbox grouping.</p>
    </body>
    </html>
    We created the grid the same as the original example except for the grouping feature:

    groupgridexample.js
    Code:
    Ext.require([
        'Ext.data.*', 
        'Ext.grid.*'
    ]);
    
    Ext.onReady(function() {
        // wrapped in closure to prevent global vars.
        Ext.define('Restaurant', {
            extend: 'Ext.data.Model',
            fields: ['name', 'cuisine']
        });
    
        var Restaurants = Ext.create('Ext.data.Store', {
            storeId: 'restaraunts',
            model: 'Restaurant',
            sorters: ['cuisine','name'],
            groupField: 'cuisine',
            data: [{
                name: 'Cheesecake Factory',
                cuisine: 'American'
            },{
                name: 'University Cafe',
                cuisine: 'American'
            },{
                name: 'Slider Bar',
                cuisine: 'American'
            },{
                name: 'Shokolaat',
                cuisine: 'American'
            },{
                name: 'Gordon Biersch',
                cuisine: 'American'
            },{
                name: 'Crepevine',
                cuisine: 'American'
            },{
                name: 'Creamery',
                cuisine: 'American'
            },{
                name: 'Old Pro',
                cuisine: 'American'
            },{
                name: 'Nola\'s',
                cuisine: 'Cajun'
            },{
                name: 'House of Bagels',
                cuisine: 'Bagels'
            },{
                name: 'The Prolific Oven',
                cuisine: 'Sandwiches'
            },{
                name: 'La Strada',
                cuisine: 'Italian'
            },{
                name: 'Buca di Beppo',
                cuisine: 'Italian'
            },{
                name: 'Pasta?',
                cuisine: 'Italian'
            },{
                name: 'Madame Tam',
                cuisine: 'Asian'
            },{
                name: 'Sprout Cafe',
                cuisine: 'Salad'
            },{
                name: 'Pluto\'s',
                cuisine: 'Salad'
            },{
                name: 'Junoon',
                cuisine: 'Indian'
            },{
                name: 'Bistro Maxine',
                cuisine: 'French'
            },{
                name: 'Three Seasons',
                cuisine: 'Vietnamese'
            },{
                name: 'Sancho\'s Taquira',
                cuisine: 'Mexican'
            },{
                name: 'Reposado',
                cuisine: 'Mexican'
            },{
                name: 'Siam Royal',
                cuisine: 'Thai'
            },{
                name: 'Krung Siam',
                cuisine: 'Thai'
            },{
                name: 'Thaiphoon',
                cuisine: 'Thai'
            },{
                name: 'Tamarine',
                cuisine: 'Vietnamese'
            },{
                name: 'Joya',
                cuisine: 'Tapas'
            },{
                name: 'Jing Jing',
                cuisine: 'Chinese'
            },{
                name: 'Patxi\'s Pizza',
                cuisine: 'Pizza'
            },{
                name: 'Evvia Estiatorio',
                cuisine: 'Mediterranean'
            },{
                name: 'Cafe 220',
                cuisine: 'Mediterranean'
            },{
                name: 'Cafe Renaissance',
                cuisine: 'Mediterranean'
            },{
                name: 'Kan Zeman',
                cuisine: 'Mediterranean'
            },{
                name: 'Gyros-Gyros',
                cuisine: 'Mediterranean'
            },{
                name: 'Mango Caribbean Cafe',
                cuisine: 'Caribbean'
            },{
                name: 'Coconuts Caribbean Restaurant &amp; Bar',
                cuisine: 'Caribbean'
            },{
                name: 'Rose &amp; Crown',
                cuisine: 'English'
            },{
                name: 'Baklava',
                cuisine: 'Mediterranean'
            },{
                name: 'Mandarin Gourmet',
                cuisine: 'Chinese'
            },{
                name: 'Bangkok Cuisine',
                cuisine: 'Thai'
            },{
                name: 'Darbar Indian Cuisine',
                cuisine: 'Indian'
            },{
                name: 'Mantra',
                cuisine: 'Indian'
            },{
                name: 'Janta',
                cuisine: 'Indian'
            },{
                name: 'Hyderabad House',
                cuisine: 'Indian'
            },{
                name: 'Starbucks',
                cuisine: 'Coffee'
            },{
                name: 'Peet\'s Coffee',
                cuisine: 'Coffee'
            },{
                name: 'Coupa Cafe',
                cuisine: 'Coffee'
            },{
                name: 'Lytton Coffee Company',
                cuisine: 'Coffee'
            },{
                name: 'Il Fornaio',
                cuisine: 'Italian'
            },{
                name: 'Lavanda',
                cuisine: 'Mediterranean'
            },{
                name: 'MacArthur Park',
                cuisine: 'American'
            },{
                name: 'St Michael\'s Alley',
                cuisine: 'Californian'
            },{
                name: 'Cafe Renzo',
                cuisine: 'Italian'
            },{
                name: 'Osteria',
                cuisine: 'Italian'
            },{
                name: 'Vero',
                cuisine: 'Italian'
            },{
                name: 'Cafe Renzo',
                cuisine: 'Italian'
            },{
                name: 'Miyake',
                cuisine: 'Sushi'
            },{
                name: 'Sushi Tomo',
                cuisine: 'Sushi'
            },{
                name: 'Kanpai',
                cuisine: 'Sushi'
            },{
                name: 'Pizza My Heart',
                cuisine: 'Pizza'
            },{
                name: 'New York Pizza',
                cuisine: 'Pizza'
            },{
                name: 'California Pizza Kitchen',
                cuisine: 'Pizza'
            },{
                name: 'Round Table',
                cuisine: 'Pizza'
            },{
                name: 'Loving Hut',
                cuisine: 'Vegan'
            },{
                name: 'Garden Fresh',
                cuisine: 'Vegan'
            },{
                name: 'Cafe Epi',
                cuisine: 'French'
            },{
                name: 'Tai Pan',
                cuisine: 'Chinese'
            }]
        });
        
        var groupingFeature = Ext.create('Ext.grid.feature.CheckGrouping',{
            groupHeaderTpl: 'CUISINE: {name} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
        });
    
        var sm = Ext.create('Ext.selection.CheckboxModel', { checkOnly: true});
    
        var grid = Ext.create('Ext.grid.Panel', {
            
            
            listeners: {
            },
            
            
            renderTo: Ext.getBody(),
            selModel: sm,
            collapsible: true,
            iconCls: 'icon-grid',
            frame: true,
            store: Restaurants,
            width: 600,
            height: 400,
            title: 'Restaurants',
            features: [groupingFeature],
            columns: [{
                text: 'Name',
                flex: 1,
                dataIndex: 'name'
            },{
                text: 'Cuisine',
                flex: 1,
                dataIndex: 'cuisine'
            }],
            fbar  : ['->', {
                text:'Clear Grouping',
                iconCls: 'icon-clear-group',
                handler : function(){
                    groupingFeature.disable();
                }
            }]
        });
    });
    Finally we created the plugin that actually does the checkbox grouping.

    checkGrouping.js
    Code:
    /**
     */
    Ext.define('Ext.grid.feature.CheckGrouping', {
        extend: 'Ext.grid.feature.Grouping',
        requires: 'Ext',
        alias: 'feature.brigrouping',
    
        constructor: function() {
           this.callParent(arguments);
           this.groupHeaderTpl = ['<dl style="height:18px; border:0px !important">',
                 '<dd id="groupcheck{name}" class="x-grid-row-checker x-column-header-text" style="width:18px; float:left;" x-grid-group-hd-text="{text}">&nbsp;</dd>',
                 '<dd style="float:left; padding:3px 0px 0px 3px;">',
                 this.groupHeaderTpl,
                 '</dd>',
                 '</dl>'
                 ].join('');
        },  
    
        onGroupClick: function(view, node, group, e, options ) {     
             var checkbox = Ext.get('groupcheck'+group); 
             if(this.inCheckbox(checkbox, e.getXY())) {         
                  this.toggleCheckbox(group,node, view);     
             } else if(this.isLeftofCheckbox(checkbox, e.getXY())) {
                  this.callParent(arguments);     
             } 
        },    
    
        inCheckbox: function(checkbox, xy) {
            var x = xy[0];
            var y = xy[1];
            if(x >= checkbox.getLeft() &&
                x <= checkbox.getRight() &&
                y >= checkbox.getTop() &&
                y <= checkbox.getBottom()) {
                return true;
            }
            return false;
        },
        isLeftofCheckbox: function(checkbox, xy) {
            if(xy[0] < checkbox.getLeft()) {
                return true;
            }
            return false;
        },
        toggleCheckbox: function(group, node, view) {
            var classes = node.classList;
            var nodeEl = Ext.get(node);
            var addingCheck;
            if(!classes.contains('x-grid-row-checked')) {
                nodeEl.addCls('x-grid-row-checked');
                addingCheck = true;
            }
            else {
                nodeEl.removeCls('x-grid-row-checked');
                addingCheck = false;
            }
            var sm = view.getSelectionModel();
            var ds = sm.store;
    
            var records = ds.queryBy(
                function(record, id) {
                    if(record.data[ds.groupField] == group) {
                        if(addingCheck) {
                            sm.select(record, true);
                        }
                        else {
                            sm.deselect(record);
                        }
                    }
                }, this
            );
        }
    });
    Finally, we didn't change any of the css rules from the Grouping example:
    Code:
    /*!
     * Ext JS
     * Copyright(c) 2006-2011 Sencha Inc.
     * licensing@sencha.com
     * http://www.sencha.com/license
     */
    body {
        font-family:helvetica,tahoma,verdana,sans-serif;
        padding:20px;
        padding-top:32px;
        font-size:13px;
    }
    p {
        margin-bottom:15px;
    }
    h1 {
        font-size:18px;
        margin-bottom:20px;
    }
    h2 {
        font-size:14px;
        color:#333;
        font-weight:bold;
        margin:10px 0;
    }
    .example-info{
        width:150px;
        border:1px solid #c3daf9;
        border-top:1px solid #DCEAFB;
        border-left:1px solid #DCEAFB;
        background:#ecf5fe url( info-bg.gif ) repeat-x;
        font-size:10px;
        padding:8px;
    }
    pre.code{
        background: #F8F8F8;
        border: 1px solid #e8e8e8;
        padding:10px;
        margin:10px;
        margin-left:0px;
        border-left:5px solid #e8e8e8;
        font-size: 12px !important;
        line-height:14px !important;
    }
    .msg .x-box-mc {
        font-size:14px;
    }
    #msg-div {
        position:absolute;
        left:35%;
        top:10px;
        width:300px;
        z-index:20000;
    }
    #msg-div .msg {
        border-radius: 8px;
        -moz-border-radius: 8px;
        background: #F6F6F6;
        border: 2px solid #ccc;
        margin-top: 2px;
        padding: 10px 15px;
        color: #555;
    }
    #msg-div .msg h3 {
        margin: 0 0 8px;
        font-weight: bold;
        font-size: 15px;
    }
    #msg-div .msg p {
        margin: 0;
    }
    .x-grid3-row-body p {
        margin:5px 5px 10px 5px !important;
    }
    
    .feature-list {
        margin-bottom: 15px;
    }
    .feature-list li {
        list-style: disc;
        margin-left: 17px;
        margin-bottom: 4px;
    }
    It looks like this:

    checkGrouping.jpg

    The expanding and collapsing isn't great as it just responds to anything left of the checkbox.


    I would love feedback on this.
    Last edited by bain4111; 14 Jul 2011 at 7:12 AM. Reason: Incorporated Ejarque's changes

  3. #3
    Sencha User
    Join Date
    Jul 2009
    Posts
    48
    Vote Rating
    0
    ninoguba is on a distinguished road

      0  

    Default

    Nice effort. Still would prefer group header checkboxes to line up with the row's checkboxes to make it look more seamless.

  4. #4
    Sencha User
    Join Date
    Jul 2011
    Posts
    1
    Vote Rating
    0
    janeDang is on a distinguished road

      0  

    Default Hi All.I test that with ext-4.0.0.

    I test it with ext-4.0.0,now have one issue like that,when click group checkbox ,can't get xy when inCheckbox is called.Just need ext-4.0.2?

    Thank you.

  5. #5
    Ext JS Premium Member
    Join Date
    May 2011
    Posts
    1
    Vote Rating
    0
    Salvador Ejarque is on a distinguished road

      0  

    Default

    I realized that the problem is that e.xy is always null.
    I tried to fix it by changing the two references to "e.xy" with "e.getXY ()" and it worked!

    the modified onGroupClick method :
    HTML Code:
    [...]
    onGroupClick: function(view, node, group, e, options ) {
        var checkbox = Ext.get('groupcheck'+group);
        if(this.inCheckbox(checkbox, e.getXY())) {
            this.toggleCheckbox(group,node, view);
        } else if(this.isLeftofCheckbox(checkbox, e.getXY())) {
            this.callParent(arguments);
        }
    },
    [...]

  6. #6
    Ext JS Premium Member
    Join Date
    Apr 2010
    Location
    Sandy, UTah
    Posts
    4
    Vote Rating
    0
    bain4111 is on a distinguished road

      0  

    Default

    I went ahead and incorporated Salvador's change into my code above.

  7. #7
    Sencha Premium Member
    Join Date
    Aug 2011
    Posts
    8
    Vote Rating
    0
    tbonci is on a distinguished road

      0  

    Default Tweaked slightly

    I appreciate the work that others have put into this. I've made some modifications:

    Checkboxes now line up with the other checkboxes from a checkbox model.
    The expander is now to the right of the checkbox and now clicking on the expander expands/collapses the header, rather than clicking to the left of the checkbox.
    I removed the function that tested if the click event was to the left of the checkbox, as it was no longer used.
    I changed from using the groupField of the store to using the first grouper that is associated with the store.

    The only downside is that the feature now needs to know the height and width of the images used for the expander/collapser.

    Code:
    Ext.define('Ext.grid.feature.CheckGrouping', {
            extend: 'Ext.grid.feature.Grouping',
            requires: 'Ext',
            alias: 'feature.brigrouping',
    constructor: function () {
            this.callParent(arguments);
            this.groupHeaderTpl = ['<dl style="height:18px; border:0px !important">',
                 '<dd id="groupcheck{name}" class="x-grid-row-checker x-column-header-text" style="width:18px; float:left; margin-left: -1px;" x-grid-group-hd-text="{text}">&nbsp;</dd>',
                 '<dd style="float:left; padding:3px 0px 0px 3px; margin-left: 20px;">',
                 this.groupHeaderTpl,
                 '</dd>',
                 '</dl>'
                 ].join('');
        },
        
        expanderXPos: 20,
        expanderYPos: -1,
        expanderImageWidth: 9,
        expanderImageHeight: 15,
        
        getFeatureTpl: function (values, parent, x, xcount) {
            var me = this;
            
            return [
                '<tpl if="typeof rows !== \'undefined\'">',
                    '<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 style = "background-position: ' + me.expanderXPos + 'px ' + me.expanderYPos + 'px; padding: 0;" class="' + Ext.baseCSSPrefix + 'grid-group-title">{collapsed}' + me.groupHeaderTpl + '</div></div></td></tr>',
                    '<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('');
        },
    
        onGroupClick: function (view, node, group, e, options) {     
            var checkbox = Ext.get('groupcheck' + group);
            if (this.inCheckbox(checkbox, e.getXY())) {         
                this.toggleCheckbox(group, node, view);     
            } else if (this.inExpander(checkbox, e.getXY())) {
                this.callParent(arguments);     
            } 
        },    
    
        inCheckbox: function (checkbox, xy) {
            var x = xy[0],
                y = xy[1];
            if (x >= checkbox.getLeft() &&
                x <= checkbox.getRight() &&
                y >= checkbox.getTop() &&
                y <= checkbox.getBottom()) {
                return true;
            }
            return false;
        },
        
        inExpander: function (checkbox, xy) {
            var expanderLeft = checkbox.getWidth() + checkbox.getLeft() + (this.expanderXPos - checkbox.getWidth()),
                expanderRight = expanderLeft + this.expanderImageWidth,
                expanderTop = checkbox.getTop() + this.expanderYPos,
                expanderBottom = expanderTop + this.expanderImageHeight,
                x = xy[0],
                y = xy[1];
            if (x >= expanderLeft && x <= expanderRight && y >= expanderTop && y <= expanderBottom) {
                return true;
            }
            return false;
        },
        toggleCheckbox: function (group, node, view) {
            var nodeEl = Ext.get(node),
                sm = view.getSelectionModel(),
                ds = sm.store,
                grouperName,
                addingCheck,
                records;
            if (!Ext.isEmpty(ds.groupers)) {
                grouperName = ds.groupers.items[0].property;
            }
            if (!nodeEl.hasCls('x-grid-row-checked')) {
                nodeEl.addCls('x-grid-row-checked');
                addingCheck = true;
            } else {
                nodeEl.removeCls('x-grid-row-checked');
                addingCheck = false;
            }
            records = ds.queryBy(
                function (record, id) {
                    if (record.data[grouperName] === group) {
                        if (addingCheck) {
                            sm.select(record, true);
                        } else {
                            sm.deselect(record);
                        }
                    }
                }, this
            );
        }
    });
    I hope this can be of some help to others.
    Last edited by tbonci; 13 Dec 2011 at 10:43 AM. Reason: Fixed a bug with IE that was caused by checking for classes,

  8. #8
    Sencha User
    Join Date
    Feb 2009
    Posts
    5
    Vote Rating
    3
    Steferson Patake is on a distinguished road

      1  

    Default

    hi people, this modification is just perfect for what i´m looking for.
    but i need the same action to other columns of the same grid, selecting only that checkboxes of that group as well.

    something like this:
    checkcolumn.jpg

    thanks in advance