Skip to content

Commit

Permalink
AG-8439 - SSRM groupIncludeTotalFooter
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewGlazier committed Feb 29, 2024
1 parent 2928bbb commit 6fde407
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 6 deletions.
42 changes: 41 additions & 1 deletion community-modules/core/src/interfaces/iServerSideDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface IServerSideGetRowsParams<TData = any, TContext = any> extends A

/**
* The parent row node. The RootNode (level -1) if request is top level.
* This is NOT part fo the request as it cannot be serialised to JSON (a rowNode has methods).
* This is NOT part of the request as it cannot be serialised to JSON (a rowNode has methods).
*/
parentNode: IRowNode;

Expand All @@ -58,13 +58,53 @@ export interface IServerSideGetRowsParams<TData = any, TContext = any> extends A

}

export interface IServerSideGetTotalFooterDataRequest {
/** Columns that are currently row grouped. */
rowGroupCols: ColumnVO[];
/** Columns that have aggregations on them. */
valueCols: ColumnVO[];
/** Columns that have pivot on them. */
pivotCols: ColumnVO[];
/** Defines if pivot mode is on or off. */
pivotMode: boolean;
/**
* If filtering, what the filter model is.
* If Advanced Filter is enabled, will be of type `AdvancedFilterModel | null`.
* If Advanced Filter is disabled, will be of type `FilterModel`.
*/
filterModel: FilterModel | AdvancedFilterModel | null;
}

export interface IServerSideGetTotalFooterDataParams<TData = any, TContext = any> extends AgGridCommon<TData, TContext> {
/**
* Details for the request. A simple object that can be converted to JSON.
*/
request: IServerSideGetTotalFooterDataRequest;

/**
* Success callback, pass the row data back to the grid that was requested.
*/
success(data: any): void;

/**
* Fail callback, tell the grid the call failed so it can adjust it's state.
*/
fail(): void;

}

// datasource for Server Side Row Model
export interface IServerSideDatasource {
/**
* Grid calls `getRows` when it requires more rows as specified in the params.
* Params object contains callbacks for responding to the request.
*/
getRows(params: IServerSideGetRowsParams): void;
/**
* Only required if using `groupIncludeTotalFooter`
* Grid calls `getTotalFooterData` when it requires the total footer data.
*/
getTotalFooterData?(params: IServerSideGetTotalFooterDataParams): void;
/** Optional method, if your datasource has state it needs to clean up. */
destroy?(): void;
}
2 changes: 1 addition & 1 deletion community-modules/core/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export { IInfiniteRowModel } from "./interfaces/iInfiniteRowModel";

export { ColumnVO } from "./interfaces/iColumnVO";

export { IServerSideDatasource, IServerSideGetRowsParams, IServerSideGetRowsRequest } from "./interfaces/iServerSideDatasource";
export { IServerSideDatasource, IServerSideGetRowsParams, IServerSideGetRowsRequest, IServerSideGetTotalFooterDataParams, IServerSideGetTotalFooterDataRequest } from "./interfaces/iServerSideDatasource";
export { IServerSideRowModel, IServerSideTransactionManager, RefreshServerSideParams } from "./interfaces/iServerSideRowModel";
export { IServerSideStore, StoreRefreshAfterParams, ServerSideGroupLevelState } from "./interfaces/IServerSideStore";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ const GRID_OPTION_VALIDATIONS: Validations<GridOptions> = {
},
},
groupIncludeTotalFooter: {
supportedRowModels: ['clientSide'],
supportedRowModels: ['clientSide', 'serverSide'],
},
groupRemoveSingleChildren: {
dependencies: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// This fake server uses http://alasql.org/ to mimic how a real server
// might generate sql queries from the Server-Side Row Model request.
// To keep things simple it does the bare minimum to support the example.
function FakeServer(allData) {
alasql.options.cache = false;

return {
getTotalData: function(request) {
const query = `${selectSql(request)} FROM ? ${whereSql(request)}`;
return alasql(query, [allData])[0];
},
getData: function(request) {
var results = executeQuery(request);

return {
success: true,
rows: results,
lastRow: getLastRowIndex(request)
};
}
};

function executeQuery(request) {
var sql = buildSql(request);

console.log('[FakeServer] - about to execute query:', sql);

return alasql(sql, [allData]);
}

function buildSql(request) {
return selectSql(request) + ' FROM ?' + whereSql(request) + groupBySql(request) + orderBySql(request) + limitSql(request);
}

function selectSql(request) {
var rowGroupCols = request.rowGroupCols;
var valueCols = request.valueCols;
var groupKeys = request.groupKeys;

const isTotalFooter = (rowGroupCols && !groupKeys);
if (isTotalFooter || isDoingGrouping(rowGroupCols, groupKeys)) {
var colsToSelect = [];
if (groupKeys) {
var rowGroupCol = rowGroupCols[groupKeys.length];
colsToSelect.push(rowGroupCol.id);
}

valueCols.forEach(function(valueCol) {
colsToSelect.push(valueCol.aggFunc + '(' + valueCol.id + ') AS ' + valueCol.id);
});

return 'SELECT ' + colsToSelect.join(', ');
}

return 'SELECT *';
}

function whereSql(request) {
var rowGroups = request.rowGroupCols;
var groupKeys = request.groupKeys;
var whereParts = [];

if (groupKeys) {
groupKeys.forEach(function(key, i) {
var value = typeof key === 'string' ? "'" + key + "'" : key;

whereParts.push(rowGroups[i].id + ' = ' + value);
});
}

if (whereParts.length > 0) {
return ' WHERE ' + whereParts.join(' AND ');
}

return '';
}

function groupBySql(request) {
var rowGroupCols = request.rowGroupCols;
var groupKeys = request.groupKeys;

if (isDoingGrouping(rowGroupCols, groupKeys)) {
var rowGroupCol = rowGroupCols[groupKeys.length];

return ' GROUP BY ' + rowGroupCol.id + ' HAVING count(*) > 0';
}

return '';
}

function orderBySql(request) {
var sortModel = request.sortModel;

if (sortModel.length === 0) return '';

var sorts = sortModel.map(function(s) {
return s.colId + ' ' + s.sort.toUpperCase();
});

return ' ORDER BY ' + sorts.join(', ');
}

function limitSql(request) {
if (request.endRow == undefined || request.startRow == undefined) { return ''; }
var blockSize = request.endRow - request.startRow;

return ' LIMIT ' + blockSize + ' OFFSET ' + request.startRow;
}

function isDoingGrouping(rowGroupCols, groupKeys) {
// we are not doing grouping if at the lowest level
return rowGroupCols.length > groupKeys.length;
}

function getLastRowIndex(request) {
return executeQuery({ ...request, startRow: undefined, endRow: undefined }).length;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="example-wrapper">
<div id="myGrid" class="ag-theme-quartz"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/alasql.min.js"></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
GridApi,
createGrid,
GetRowIdParams,
GridOptions,
IServerSideDatasource,
IServerSideGetRowsParams,
IsServerSideGroupOpenByDefaultParams,
IServerSideGetTotalFooterDataParams,
} from '@ag-grid-community/core';
declare var FakeServer: any;
import { RowGroupingModule } from '@ag-grid-enterprise/row-grouping';
import { ServerSideRowModelModule } from '@ag-grid-enterprise/server-side-row-model';
import { ModuleRegistry } from "@ag-grid-community/core";

ModuleRegistry.registerModules([RowGroupingModule, ServerSideRowModelModule]);

let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
columnDefs: [
{ field: 'country', enableRowGroup: true, rowGroup: true, hide: true },
{ field: 'sport', enableRowGroup: true, rowGroup: true, hide: true },
{ field: 'year', minWidth: 100 },
{ field: 'gold', aggFunc: 'sum', enableValue: true },
{ field: 'silver', aggFunc: 'sum', enableValue: true },
{ field: 'bronze', aggFunc: 'sum', enableValue: true },
],
defaultColDef: {
flex: 1,
minWidth: 120,
},
autoGroupColumnDef: {
flex: 1,
minWidth: 280,
},
rowModelType: 'serverSide',
isServerSideGroupOpenByDefault: isServerSideGroupOpenByDefault,
groupIncludeTotalFooter: true,
getRowId: getRowId,
};

function getRowId(params: GetRowIdParams) {
return Math.random().toString();
}

function isServerSideGroupOpenByDefault(
params: IsServerSideGroupOpenByDefaultParams
) {
var route = params.rowNode.getRoute();
if (!route) {
return false;
}

var routeAsString = route.join(',');

var routesToOpenByDefault = ['Zimbabwe', 'Zimbabwe,Swimming'];

return routesToOpenByDefault.indexOf(routeAsString) >= 0;
}

function getServerSideDatasource(server: any): IServerSideDatasource {
return {
getTotalFooterData: (params: IServerSideGetTotalFooterDataParams) => {
const response = server.getTotalData(params.request);
params.success(response);
},
getRows: (params: IServerSideGetRowsParams) => {
console.log('[Datasource] - rows requested by grid: ', params.request);

var response = server.getData(params.request);

// adding delay to simulate real server call
setTimeout(() => {
if (response.success) {
// call the success callback
params.success({
rowData: response.rows,
rowCount: response.lastRow,
});
} else {
// inform the grid request failed
params.fail();
}
}, 400);
},
};
}

// setup the grid after the page has finished loading
var gridDiv = document.querySelector<HTMLElement>('#myGrid')!;
gridApi = createGrid(gridDiv, gridOptions);

fetch('https://www.ag-grid.com/example-assets/olympic-winners.json')
.then((response) => response.json())
.then(function (data) {
// setup the fake server with entire dataset
var fakeServer = new FakeServer(data);

// create datasource with a reference to the fake server
var datasource = getServerSideDatasource(fakeServer);

// register the datasource with the grid
gridApi!.setGridOption('serverSideDatasource', datasource);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}

#myGrid {
flex: 1 1 auto;
width: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,20 @@ By default the group nodes stick to the top of the Grid, to disable this behavio

## Row Group Footers

To enable [Row Group Footers](./grouping-footers/), set the `groupIncludeFooter` property to true. Note that the total footer is not supported by the SSRM.
To enable [Row Group Footers](./grouping-footers/), set the `groupIncludeFooter` property to true.

{% gridExampleRunner title="Group Footers" name="group-footer" type="generated" options={ "enterprise": true, "modules": ["serverside","rowgrouping"] } /%}

Row group footers can also be used with `groupDisplayType='multipleColumns'`, as demonstrated in the example below.

{% gridExampleRunner title="Multiple Group Columns and Footers" name="group-footer-multiple-cols" type="generated" options={ "enterprise": true, "modules": ["serverside","rowgrouping"] } /%}

## Total Footer

To enable [Total Footers](./grouping-footers/), set the `groupIncludeTotalFooter` property to true, and provide a datasource `getTotalFooterData` implementation.

{% gridExampleRunner title="Total Footer" name="total-footer" type="generated" options={ "enterprise": true, "modules": ["serverside","rowgrouping"] } /%}

## Expand All / Collapse All

It is possible to expand and collapse all group rows using the `expandAll()` and `collapseAll()` grid API's.
Expand Down

0 comments on commit 6fde407

Please sign in to comment.