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); + }); + }); +});