diff --git a/src/examples/slickgrid/App.tsx b/src/examples/slickgrid/App.tsx
index d7cc681f..7abd763f 100644
--- a/src/examples/slickgrid/App.tsx
+++ b/src/examples/slickgrid/App.tsx
@@ -36,6 +36,7 @@ import Example33 from './Example33';
import Example34 from './Example34';
import Example35 from './Example35';
import Example36 from './Example36';
+import Example37 from './Example37';
const routes: Array<{ path: string; route: string; component: any; title: string; }> = [
{ path: 'example1', route: '/example1', component: , title: '1- Basic Grid / 2 Grids' },
@@ -72,6 +73,7 @@ const routes: Array<{ path: string; route: string; component: any; title: string
{ path: 'example34', route: '/example34', component: , title: '34- Real-Time Trading Platform' },
{ path: 'example35', route: '/example35', component: , title: '35- Row Based Editing' },
{ path: 'example36', route: '/example36', component: , title: '36- Excel Export Formulas' },
+ { path: 'example37', route: '/example37', component: , title: '37- Footer Totals Row' },
];
export default function Routes() {
diff --git a/src/examples/slickgrid/Example37.tsx b/src/examples/slickgrid/Example37.tsx
new file mode 100644
index 00000000..c476841f
--- /dev/null
+++ b/src/examples/slickgrid/Example37.tsx
@@ -0,0 +1,181 @@
+import {
+ type Column,
+ FieldType,
+ type GridOption,
+ SlickgridReact,
+ Editors,
+ type OnCellChangeEventArgs,
+ type SlickgridReactInstance,
+} from '../../slickgrid-react';
+import React from 'react';
+import BaseSlickGridState from './state-slick-grid-base';
+
+const NB_ITEMS = 100;
+
+interface State extends BaseSlickGridState { }
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+interface Props { }
+
+export default class Example2 extends React.Component {
+ private _darkMode = false;
+
+ title = 'Example 37: Footer Totals Row';
+ subTitle = `Display a totals row at the end of the grid.`;
+
+ reactGrid!: SlickgridReactInstance;
+ resizerPaused = false;
+
+ constructor(public readonly props: Props) {
+ super(props);
+
+ this.state = {
+ gridOptions: undefined,
+ columnDefinitions: [],
+ dataset: [],
+ };
+ }
+
+ componentDidMount() {
+ document.title = this.title;
+
+ // define the grid options & columns and then create the grid itself
+ this.defineGrid();
+ }
+
+ componentWillUnmount() {
+ document.querySelector('.panel-wm-content')!.classList.remove('dark-mode');
+ document.querySelector('#demo-container')!.dataset.bsTheme = 'light';
+ }
+
+ reactGridReady(reactGrid: SlickgridReactInstance) {
+ this.reactGrid = reactGrid;
+ this.updateAllTotals();
+ }
+
+ /* Define grid Options and Columns */
+ defineGrid() {
+ const columnDefs: Column[] = [];
+ for (let i = 0; i < 10; i++) {
+ columnDefs.push({
+ id: i,
+ name: String.fromCharCode('A'.charCodeAt(0) + i),
+ field: String(i),
+ type: FieldType.number,
+ width: 58,
+ editor: { model: Editors.integer }
+ });
+ }
+
+ const gridOptions: GridOption = {
+ autoEdit: true,
+ autoCommitEdit: true,
+ editable: true,
+ darkMode: this._darkMode,
+ gridHeight: 450,
+ gridWidth: 800,
+ enableCellNavigation: true,
+ rowHeight: 30,
+ createFooterRow: true,
+ showFooterRow: true,
+ footerRowHeight: 28,
+ };
+
+ this.setState((state: State, props: Props) => ({
+ ...this.state,
+ columnDefinitions: columnDefs,
+ gridOptions,
+ dataset: this.loadData(NB_ITEMS, columnDefs.length),
+ }));
+ }
+
+ loadData(itemCount: number, colDefLn: number) {
+ // mock a dataset
+ const datasetTmp: any[] = [];
+ for (let i = 0; i < itemCount; i++) {
+ const d = (datasetTmp[i] = {} as any);
+ d.id = i;
+ for (let j = 0; j < colDefLn; j++) {
+ d[j] = Math.round(Math.random() * 10);
+ }
+ }
+ return datasetTmp;
+ }
+
+ handleOnCellChange(_e: Event, args: OnCellChangeEventArgs) {
+ this.updateTotal(args.cell);
+ }
+
+ handleOnColumnsReordered() {
+ this.updateAllTotals();
+ }
+
+ toggleDarkMode() {
+ this._darkMode = !this._darkMode;
+ this.toggleBodyBackground();
+ this.reactGrid.slickGrid?.setOptions({ darkMode: this._darkMode });
+ this.updateAllTotals();
+ }
+
+ toggleBodyBackground() {
+ if (this._darkMode) {
+ document.querySelector('.panel-wm-content')!.classList.add('dark-mode');
+ document.querySelector('#demo-container')!.dataset.bsTheme = 'dark';
+ } else {
+ document.querySelector('.panel-wm-content')!.classList.remove('dark-mode');
+ document.querySelector('#demo-container')!.dataset.bsTheme = 'light';
+ }
+ }
+
+ updateAllTotals() {
+ let columnIdx = this.reactGrid.slickGrid?.getColumns().length || 0;
+ while (columnIdx--) {
+ this.updateTotal(columnIdx);
+ }
+ }
+
+ updateTotal(cell: number) {
+ const columnId = this.reactGrid.slickGrid?.getColumns()[cell].id as number;
+
+ let total = 0;
+ let i = this.state.dataset!.length || 0;
+ while (i--) {
+ total += (parseInt(this.state.dataset![i][columnId], 10) || 0);
+ }
+ const columnElement = this.reactGrid.slickGrid?.getFooterRowColumn(columnId);
+ if (columnElement) {
+ columnElement.textContent = `Sum: ${total}`;
+ }
+ }
+
+ render() {
+ return !this.state.gridOptions ? '' : (
+
+
+ {this.title}
+
+
+ see
+
+ code
+
+
+
+
+
+
this.reactGridReady($event.detail)}
+ onCellChange={$event => this.handleOnCellChange($event.detail.eventData, $event.detail.args)}
+ onColumnsReordered={$event => this.handleOnColumnsReordered()}
+ />
+
+ );
+ }
+}
diff --git a/test/cypress/e2e/example37.cy.ts b/test/cypress/e2e/example37.cy.ts
new file mode 100644
index 00000000..57b833db
--- /dev/null
+++ b/test/cypress/e2e/example37.cy.ts
@@ -0,0 +1,57 @@
+describe('Example 37 - Footer Totals Row', () => {
+ const fullTitles = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
+ const GRID_ROW_HEIGHT = 33;
+
+ it('should display Example title', () => {
+ cy.visit(`${Cypress.config('baseUrl')}/example37`);
+ cy.get('h2').should('contain', 'Example 37: Footer Totals Row');
+ });
+
+ it('should have exact Column Header Titles in the grid', () => {
+ cy.get('#grid37')
+ .find('.slick-header-columns:nth(0)')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(fullTitles[index]));
+ });
+
+ it('should have a total sum displayed in the footer for each column', () => {
+ for (let i = 0; i < 10; i++) {
+ cy.get(`.slick-footerrow-columns .slick-footerrow-column:nth(${i})`)
+ .should($span => {
+ const totalStr = $span.text();
+ const totalVal = Number(totalStr.replace('Sum: ', ''));
+
+ expect(totalStr).to.contain('Sum:');
+ expect(totalVal).to.gte(400);
+ });
+
+ }
+ });
+
+ it('should be able to increase cell value by a number of 5 and expect column sum to be increased by 5 as well', () => {
+ let cellVal = 0;
+ let totalVal = 0;
+ const increasingVal = 50;
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`)
+ .should($span => {
+ cellVal = Number($span.text());
+ expect(cellVal).to.gte(0);
+ });
+ cy.get('.slick-footerrow-columns .slick-footerrow-column:nth(0)')
+ .should($span => {
+ totalVal = parseInt($span.text().replace('Sum: ', ''));
+ expect(totalVal).to.gte(400);
+ });
+
+ cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).click();
+ cy.get('.editor-0').type(`${increasingVal}{enter}`);
+ cy.wait(1);
+
+ cy.get('.slick-footerrow-columns .slick-footerrow-column:nth(0)')
+ .should($span => {
+ const newTotalVal = parseInt($span.text().replace('Sum: ', ''));
+ expect(newTotalVal).to.eq(totalVal - cellVal + increasingVal);
+ });
+ });
+});