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 & Bar',
cuisine: 'Caribbean'
},{
name: 'Rose & 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}"> </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.
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}"> </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.
Powered by vBulletin® Version 4.1.5 Copyright © 2013 vBulletin Solutions, Inc. All rights reserved.