PDA

View Full Version : Checkboxes on group header rows on a group grid



bpratt65
8 Jun 2011, 2:04 PM
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!

bain4111
13 Jun 2011, 10:03 AM
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:

<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

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


/**
*/
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:

/*!
* 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:

26569

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


I would love feedback on this.

ninoguba
13 Jun 2011, 12:58 PM
Nice effort. Still would prefer group header checkboxes to line up with the row's checkboxes to make it look more seamless.

janeDang
10 Jul 2011, 8:37 PM
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.

Salvador Ejarque
13 Jul 2011, 9:17 AM
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 :


[...]
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);
}
},
[...]

bain4111
14 Jul 2011, 7:14 AM
I went ahead and incorporated Salvador's change into my code above.

tbonci
16 Nov 2011, 12:17 PM
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.



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.

Steferson Patake
26 Mar 2013, 10:52 AM
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:
42715

thanks in advance