Grid grouping and sorting in ExtJS 4.2.1 - extjs

I try to implement grid with grouping similar to this example:
http://dev.sencha.com/deploy/ext-4.0.0/examples/grid/groupgrid.html
Here data is grouped by column "Cuisine" and sorting by this column sort groups accordingly.
When I paste code of this example into a project, which uses 4.2.1, or in code editor at ExtJS 4.2.1 docs site, the view is exactly the same, sorting works for column "Name", but it doesn't work by column "Cuisine".
Did they remove sorting by grouping column in 4.2.1? If not, how to make it work?

The same example is present in 4.2.1 SDK, and indeed sorting by the grouped column doesn't work anymore. Sounds like a regression to me, you should notify Sencha.
Edit:
That's the code of the method Ext.data.Store#sort that has changed. Restoring the previous version fixes the behaviors (see my comments to find the modified lines):
Ext.define(null, {
override: 'Ext.data.Store'
,sort: function(sorters, direction, where, doSort) {
var me = this,
sorter,
newSorters;
if (Ext.isArray(sorters)) {
doSort = where;
where = direction;
newSorters = sorters;
}
else if (Ext.isObject(sorters)) {
doSort = where;
where = direction;
newSorters = [sorters];
}
else if (Ext.isString(sorters)) {
sorter = me.sorters.get(sorters);
if (!sorter) {
sorter = {
property : sorters,
direction: direction
};
newSorters = [sorter];
}
else if (direction === undefined) {
sorter.toggle();
}
else {
sorter.setDirection(direction);
}
}
if (newSorters && newSorters.length) {
newSorters = me.decodeSorters(newSorters);
if (Ext.isString(where)) {
if (where === 'prepend') {
// <code from 4.2.1>
// me.sorters.insert(0, newSorters);
// </code from 4.2.1>
// <code from 4.2.0>
sorters = me.sorters.clone().items;
me.sorters.clear();
me.sorters.addAll(newSorters);
me.sorters.addAll(sorters);
// </code from 4.2.0>
}
else {
me.sorters.addAll(newSorters);
}
}
else {
me.sorters.clear();
me.sorters.addAll(newSorters);
}
}
if (doSort !== false) {
me.fireEvent('beforesort', me, newSorters);
me.onBeforeSort(newSorters);
sorters = me.sorters.items;
if (sorters.length) {
me.doSort(me.generateComparator());
}
}
}
});

set sortable: true either on a defaults config for the grouping column or as a config on the child columns themselves. e.g.
{
// NOTE: these two are grouped columns
text: 'Close',
columns: [{
text: 'Value',
minWidth: 100,
flex: 100,
sortable: true,
dataIndex: 'ValueHeld_End'
}, {
text: 'Total',
minWidth: 110,
flex: 110,
sortable: true,
dataIndex: 'TotalPnL'
}]
}

Related

Render Grid Row Without Updating CSS File

I can render individual grid columns using a column renderer like this ;
renderer: function(val, meta) {
if (val === 'i') {
meta.style = "background-color:#86b0f4;";
}else if (val === 'n') {
meta.style = "background-color:#fcd1cc;";
}
return val
},
I would like to use this same idea on grid rows.
Unfortunately the only thing I can come across is something along the lines of :
viewConfig: {
getRowClass: function(record, rowIndex, rowParams, store){
return record.get("partytype") == 6 ? "row-highlight" : "";
}
},
This requires me to update the CSS file which I do not want to do.
Can I directly manipulate the css value on a grid row in extjs?
The meta parameter in the column renderer has a record property too. You could lookup the relevant value there instead of relying on the cell value parameter and then add a reference to the same method for each column in the configuration.
Avoiding code duplication, the renderer method could be declared anywhere but to keep this example concise I've just used an IIFE / inline declaration.
{
// Ext.grid.Panel
// ...
columns: (function(){
function columnRenderer(cellValue, meta){
var value = meta.record.get('value');
if(value === 'i')
meta.style = 'background-color:#86b0f4;';
else if(value === 'n')
meta.style = 'background-color:#fcd1cc;';
return cellValue;
}
return [
{
text: 'Foo',
dataIndex: 'foo',
renderer: columnRenderer
},
{
text: 'Bar',
dataIndex: 'bar',
renderer: columnRenderer
}
];
})()
}
ยป Fiddle

Can a subgrid be exported in angular ui-grid

I'm using the grid from http://ui-grid.info/ in a project. I've created a hierarchical grid, which works nicely, but when I do an export, it only exports the data from the top-level grid.
This is by design and is standard functionality for the grid, so there's no point in me putting up any example code. Any example from http://ui-grid.info/docs/#/tutorial will do.
Is there a way to export the subgrid (preferably both the main grid AND the subgrid together as they appear in the grid)?
Sadly the answer is no.
As you can see here the function getData iterates through all rows and then through all columns, adding to an array of extractedFields the columns to be extracted and aggregating those in an array of extractedRows.
This means that no data, other than what's defined in gridOptions' columnDef will be read, converted and extracted.
By design, subgrid information are stored inside a property of any row entity's subGridOptions but this property is never accessed inside of the exporter feature.
The motivation behind this behaviour is that expandable grid feature is still at an alpha stage, so, supporting this in other features is not a compelling priority.
Furthermore, adding subgrid to a CSV could be quite hard to design, if we wanted to provide a general solution (for example I don't even think it would be compliant to CSV standard if you had different number of columns in the main grid and in subgrids).
That said, ui-grid is an open source project, so, if you have a working design in mind, feel free to open a discussion about it on the project gitHub page or, even better, if you can design a working (and tested) solution and create a pull request, even better!
I managed to get it working, although if I had the time I would do it a bit better than what I've done by actually creating a branch of the code and doing it properly, but given time constraints, what I've got is working nicely.
FYI, here's the way I ended up getting it to do what I wanted:
In my grid options, I turned off the CSV export options in the grid menu (because I've only implemented the changes for PDF).
I made a copy of exporter.js, named it custom.exporter.js and changed my reference to point to the new file.
In custom.exporter.js, I made a copy of the getData function and named it getGridRows. getGridRows is the same as getData, except it just returns the rows object without all the stuff that gets the columns and so on. For now, I'm coding it to work with a known set of columns, so I don't need all that.
I modified the pdfExport function to be as follows:
pdfExport: function (grid, rowTypes, colTypes) {
var self = this;
var exportData = self.getGridRows(grid, rowTypes, colTypes);
var docContent = [];
$(exportData).each(function () {
docContent.push(
{
table: {
headerRows: 1,
widths: [70, 80, 150, 180],
body: [
[{ text: 'Job Raised', bold: true, fillColor: 'lightgray' }, { text: 'Job Number', bold: true, fillColor: 'lightgray' }, { text: 'Client', bold: true, fillColor: 'lightgray' }, { text: 'Job Title', bold: true, fillColor: 'lightgray' }],
[formattedDateTime(this.entity.JobDate,false), this.entity.JobNumber, this.entity.Client, this.entity.JobTitle],
]
}
});
var subGridContentBody = [];
subGridContentBody.push([{ text: 'Defect', bold: true, fillColor: 'lightgray' }, { text: 'Vendor', bold: true, fillColor: 'lightgray' }, { text: 'Status', bold: true, fillColor: 'lightgray' }, { text: 'Sign off', bold: true, fillColor: 'lightgray' }]);
$(this.entity.Defects).each(function () {
subGridContentBody.push([this.DefectName, this.DefectVendor, this.DefectStatus, '']);
});
docContent.push({
table: {
headerRows: 1,
widths: [159, 150, 50, 121],
body: subGridContentBody
}
});
docContent.push({ text: '', margin: 15 });
});
var docDefinition = {
content: docContent
}
if (self.isIE()) {
self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
} else {
pdfMake.createPdf(docDefinition).open();
}
}
No, there is no direct way to export subgrid. rather you can create youur own json data to generate csv file.
Please check the below code
function jsonToCsvConvertor(JSONData, reportTitle) {
//If JSONData is not an object then JSON.parse will parse the JSON string in an Object
var arrData = typeof JSONData !== 'object' ? JSON.parse(JSONData) : JSONData,
csv = '',
row,
key1,
i,
subGridData;
//Set Report title in first row or line
csv += reportTitle + '\r\n\n';
row = '';
for (key1 in arrData[0]) {
if(key1 !== 'subGridOptions' && key1 !== '$$hashKey'){
row += key1 + ',';
}
}
csv += row + '\r\n';
for (i = 0; i < arrData.length; i++) {
row = '';
subGridData = '';
for (key1 in arrData[i]) {
if(key1 !== 'subGridOptions' && key1 !== '$$hashKey'){
row += '"' + arrData[i][key1] + '",';
}
else if(key1 === 'subGridOptions'){
//csv += row + '\r\n';
subGridData = writeSubGridData(arrData[i][key1].data);
}
}
csv += row + '\r\n';
csv = subGridData ? csv + subGridData + '\r\n' : csv;
}
if (csv === '') {
console.log('Invalid data');
}
return csv;
}
//Generates subgrid Data to exportable form
function writeSubGridData(subgridData){
var j,
key2,
csv = '',
row = '';
for (key2 in subgridData[0]){
if(key2 !== '$$hashKey'){
row += key2 + ',';
}
}
csv = row + '\r\n';
for (j=0; j < subgridData.length ; j++){
row = '';
for(key2 in subgridData[j]){
if(key2 !== '$$hashKey'){
row += '"' + subgridData[j][key2]+ '",';
}
}
csv += row + '\r\n';
}
return csv;
}
jsonToCsvConvertor(exportData, 'New-Report');

ExtJS Change actioncolumn icon from controller / handler

TLDR: I'm using ExtJS 4 and I want to change action column buttons icon from hanlder / controller. How I can do it?
My problem: I have a menu to create a group of devices, it cointain a table of all existing devices (the device has an id, name and affiliation to the group member) with pagination and ajax store. To create a group I have to pass an array of device ids to the server.
To do this I add action column to my grid. By clicking on button in action column I want to add device id to one of the two arrays, that are stored as attributes of the grid (addedMembers and deletedMembers) and change icon in action column. At the moment, all the following code works, but I do not understand how I can change the icon?
Grid:
Ext.define('Portal.view.devicegroups.GroupDevicesGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.groupDevicesGrid',
addedMembers: [],
deletedMembers: [],
store: 'devicegroups.GroupDevicesStore',
columns: [
{
dataIndex: 'name',
flex: 1,
sortable: true,
text: Ext.String.capitalize("<?=gettext('name')?>")
},
{
xtype: 'actioncolumn',
text: Ext.String.capitalize("<?=gettext('member')?>"),
width: 75,
items: [
{
getClass: function (value, meta, record, rowIndex, colIndex) {
var cls = 'deny';
if (this.up('groupDevicesGrid').deletedMembers.indexOf(record.get('id')) !== -1 || record.get('member') == 1) {
cls = 'allow';
}
return cls;
},
handler: function (view, rowIndex, colIndex, item, event, record, row) {
this.fireEvent('changeMembership', rowIndex, record);
}
}
]
}
]
});
changeMembership method:
changeGroupDeviceMembership: function(rowIndex, device) {
var groupDevicesGrid = this.getGroupDevicesGrid(),
groupDevicesStore = groupDevicesGrid.getStore(),
addedMembers = groupDevicesGrid.addedMembers,
deletedMembers = groupDevicesGrid.deletedMembers,
deviceId = device.get('id'),
isMember = device.get('member');
if(isMember == 1) {
if(deviceId) {
if(deletedMembers.indexOf(deviceId) === -1) {
// Set allow icon
deletedMembers.push(deviceId);
} else {
// Set deny icon
deletedMembers.splice(deletedMembers.indexOf(deviceId), 1);
}
}
} else if(isMember == 0) {
if(deviceId) {
if(addedMembers.indexOf(deviceId) === -1) {
// Set deny icon
addedMembers.push(deviceId);
} else {
// Set allow icon
addedMembers.splice(deletedMembers.indexOf(deviceId), 1);
}
}
}
},
Or perhaps there is a better solution to my problem?
I am not privileged to comment so I will just take a shot at the answer. This is the way I do it..you are nearly there just add iconCls. :)
{
xtype: 'actioncolumn',
text: Ext.String.capitalize("<?=gettext('member')?>"),
width: 75,
items: [
{
iconCls:'deny', //<== try adding this icon cls
getClass: function (value, meta, record, rowIndex, colIndex) {
var cls = 'deny';
meta.tdAttr = 'data-qtip="Deny"'; //<== I like tool tips
if (this.up('groupDevicesGrid').deletedMembers.indexOf(record.get('id')) !== -1 || record.get('member') == 1) {
cls = 'allow';
meta.tdAttr = 'data-qtip="Allow"'; //<== I like tool tips
}
return cls;
},
handler: function (view, rowIndex, colIndex, item, event, record, row) {
this.fireEvent('changeMembership', rowIndex, record);
}
}
]
}
I use this pattern quite a lot, hopefully it works for you too.

angular-ui ng-grid How to make the aggregate row be an editable row

I think that what I'm trying to achieve is having a tree-like inside the ng-grid. I didn't found such an implementation but I'm wondering if I can use the grouping mechanism
I need to have the group header be editable in the same manner as the rows below it (see image above), with exactly the same editable cells, acting as a master row. When updating one cell from the header group should update all the cells beneath that group.
From ng-grid docs http://angular-ui.github.io/ng-grid/ :
default value for aggregateTemplate:
<div ng-click="row.toggleExpand()" ng-style="{'left': row.offsetleft}" class="ngAggregate">
<span class="ngAggregateText">{{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})</span>
<div class="{{row.aggClass()}}"></div>
</div>
Is it possible to use this option in order to render the aggregate row as I described?
The below answer/comment is related to tree like structure and not related to making aggregate row editable...
If you are looking for tree-like structure in ng-grid, then you could achieve that with the combination of ng-if, ng-click and API(s) that updates the ng-grid data option on click of a particular row. Here is a sample plnkr.
On click of a parent row, a toggle function is called to add/remove child rows in to the ng-grid data. (Refer to my plunker code for complete details)
$scope.toggleDisplay = function(iType) {
$scope.displayItemDetails[iType] = $scope.displayItemDetails[iType] ? 0 : 1;
$scope.selItems = $scope.updateTable();
};
$scope.updateTable = function() {
var selItems = [];
for (var i in $scope.allItems) {
var iType = $scope.allItems[i]["Type"];
if (angular.isUndefined($scope.displayItemDetails[iType])) {
$scope.displayItemDetails[iType] = 0;
}
if (1 == $scope.displayItemDetails[iType]) {
$scope.allItems[i]["Summary"] = '-';
} else {
$scope.allItems[i]["Summary"] = '+';
}
selItems.push($scope.allItems[i]);
if ($scope.displayItemDetails[iType]) {
for (var j in $scope.allItems[i]["Details"]) {
$scope.allItems[i]["Details"][j]["Summary"] = "";
selItems.push($scope.allItems[i]["Details"][j]);
}
}
}
return selItems;
};
$scope.gridOptions = {
data: 'selItems',
columnDefs: [{
field: 'Summary',
displayName: '',
cellTemplate: summaryCellTemplate,
width: 30
}, {
field: 'Name',
displayName: 'Name',
}, {
field: 'Type',
displayName: 'Type',
}, {
field: 'Cost',
displayName: 'Cost',
}, {
field: 'Quantity',
displayName: 'Quantity',
}],
enableCellSelection: false,
enableColumnResize: true
};

How can I add row numbers to an ExtJS grid?

In an ExtJS GridPanel, is there a way to design a column whose sole purpose is to act as a serial number column? This column will not need a dataIndex property.
Right now, I am using a custom row numberer function, but this means the row numberer is defined in the grid.js file and all columns from grid.ui.js needs to be copied into grid.js.
I am using the Ext designer.
EDIT: The crux of my question is: Is there a way to define a row numberer using the Ext designer?
All you need is an Ext.grid.RowNumberer in your column definition.
var colModel = new Ext.grid.ColumnModel([
new Ext.grid.RowNumberer(),
{header: "Name", width: 80, sortable: true},
{header: "Code", width: 50, sortable: true},
{header: "Description", width: 200, sortable: true}
]);
Not an answer, but just want to share this:-
On top of the Ext.grid.RowNumberer, you can have this small nifty hack which will increments your numbers correctly according to the page number that you are viewing if you have implemented PagingToolbar in your grid.
Below is my working example. I extended the original Ext.grid.RowNumberer to avoid confliction.
Kore.ux.grid.RowNumberer = Ext.extend(Ext.grid.RowNumberer, {
renderer: function(v, p, record, rowIndex) {
if (this.rowspan) {
p.cellAttr = 'rowspan="'+this.rowspan+'"';
}
var st = record.store;
if (st.lastOptions.params && st.lastOptions.params.start != undefined && st.lastOptions.params.limit != undefined) {
var page = Math.floor(st.lastOptions.params.start/st.lastOptions.params.limit);
var limit = st.lastOptions.params.limit;
return limit*page + rowIndex+1;
}else{
return rowIndex+1;
}
}
});
And the code below is the original renderer from Ext.grid.RowNumberer, which, to me, pretty ugly because the numbers is fixed all the time no matter what page number it is.
renderer : function(v, p, record, rowIndex){
if(this.rowspan){
p.cellAttr = 'rowspan="'+this.rowspan+'"';
}
return rowIndex+1;
}
For version 4.2 very very easy:
Just add a new column like this:
{
xtype: 'rownumberer',
width: 40,
sortable: false,
locked: true
}
ExtJS 4.2.1 working code below:
// Row numberer correct increasing
Ext.override(Ext.grid.RowNumberer, {
renderer: function(v, p, record, rowIndex) {
if (this.rowspan) {
p.cellAttr = 'rowspan="'+this.rowspan+'"';
}
var st = record.store;
if (st.lastOptions.page != undefined && st.lastOptions.start != undefined && st.lastOptions.limit != undefined) {
var page = st.lastOptions.page - 1;
var limit = st.lastOptions.limit;
return limit*page + rowIndex+1;
} else {
return rowIndex+1;
}
}
});

Resources