diff --git a/examples/vite-demo-vanilla-bundle/src/app-routing.ts b/examples/vite-demo-vanilla-bundle/src/app-routing.ts index f09d80d6c..32159c197 100644 --- a/examples/vite-demo-vanilla-bundle/src/app-routing.ts +++ b/examples/vite-demo-vanilla-bundle/src/app-routing.ts @@ -24,6 +24,7 @@ import Example20 from './examples/example20'; import Example21 from './examples/example21'; import Example22 from './examples/example22'; import Example23 from './examples/example23'; +import Example24 from './examples/example24'; export class AppRouting { constructor(private config: RouterConfig) { @@ -53,6 +54,7 @@ export class AppRouting { { route: 'example21', name: 'example21', view: './examples/example21.html', viewModel: Example21, title: 'Example21', }, { route: 'example22', name: 'example22', view: './examples/example22.html', viewModel: Example22, title: 'Example22', }, { route: 'example23', name: 'example23', view: './examples/example23.html', viewModel: Example23, title: 'Example23', }, + { route: 'example24', name: 'example24', view: './examples/example24.html', viewModel: Example24, title: 'Example24', }, { route: '', redirect: 'example01' }, { route: '**', redirect: 'example01' } ]; diff --git a/examples/vite-demo-vanilla-bundle/src/app.html b/examples/vite-demo-vanilla-bundle/src/app.html index 21b88503e..ab3a34936 100644 --- a/examples/vite-demo-vanilla-bundle/src/app.html +++ b/examples/vite-demo-vanilla-bundle/src/app.html @@ -105,6 +105,9 @@

Slickgrid-Universal

Example23 - Excel Export Formulas + + Example24 - Footer Totals Row + diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example20.ts b/examples/vite-demo-vanilla-bundle/src/examples/example20.ts index 12138c014..a1bcdb33f 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example20.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example20.ts @@ -16,7 +16,6 @@ export default class Example20 { dataset: any[] = []; gridOptions!: GridOption; gridContainerElm: HTMLDivElement; - isWithPagination = true; sgb: SlickVanillaGridBundle; attached() { @@ -35,7 +34,7 @@ export default class Example20 { // let's wrap the grid resize in a delay & show the grid only after the resize setTimeout(() => { this.sgb = new Slicker.GridBundle(shadowObj.gridContainer as HTMLDivElement, this.columnDefinitions, { ...ExampleGridOptions, ...this.gridOptions }, this.dataset); - this.sgb.resizerService.resizeGrid(); + this.sgb.resizerService.resizeGrid(150); shadowObj.gridContainer.style.opacity = '1'; }, 50); } @@ -124,13 +123,4 @@ export default class Example20 { return datasetTmp; } - - // Toggle the Grid Pagination - // IMPORTANT, the Pagination MUST BE CREATED on initial page load before you can start toggling it - // Basically you cannot toggle a Pagination that doesn't exist (must created at the time as the grid) - togglePagination() { - this.isWithPagination = !this.isWithPagination; - this.sgb.paginationService!.togglePaginationVisibility(this.isWithPagination); - this.sgb.slickGrid!.setSelectedRows([]); - } } diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example24.html b/examples/vite-demo-vanilla-bundle/src/examples/example24.html new file mode 100644 index 000000000..9047ae77f --- /dev/null +++ b/examples/vite-demo-vanilla-bundle/src/examples/example24.html @@ -0,0 +1,25 @@ +

+ Example 24 - Footer Totals Row + (with Salesforce Theme) + + + + + +

+ +
+ Display a totals row at the end of the grid. +
+ +
+
\ No newline at end of file diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example24.ts b/examples/vite-demo-vanilla-bundle/src/examples/example24.ts new file mode 100644 index 000000000..f40dc1841 --- /dev/null +++ b/examples/vite-demo-vanilla-bundle/src/examples/example24.ts @@ -0,0 +1,143 @@ +import { type Column, Editors, FieldType, type GridOption, SlickEventHandler, } from '@slickgrid-universal/common'; +import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; + +import { ExampleGridOptions } from './example-grid-options'; +import { BindingEventService } from '@slickgrid-universal/binding'; + +const NB_ITEMS = 100; + +export default class Example20 { + private _bindingEventService: BindingEventService; + private _darkMode = false; + private _eventHandler: SlickEventHandler; + + columnDefinitions: Column[] = []; + dataset: any[] = []; + gridOptions!: GridOption; + gridContainerElm: HTMLDivElement; + sgb: SlickVanillaGridBundle; + + constructor() { + this._bindingEventService = new BindingEventService(); + } + + attached() { + this._eventHandler = new SlickEventHandler(); + + // define the grid options & columns and then create the grid itself + this.defineGrid(); + + // mock some data (different in each dataset) + this.dataset = this.getData(NB_ITEMS); + this.gridContainerElm = document.querySelector('.grid24') as HTMLDivElement; + + this.sgb = new Slicker.GridBundle(document.querySelector('.grid24') as HTMLDivElement, this.columnDefinitions, { ...ExampleGridOptions, ...this.gridOptions }, this.dataset); + this.updateAllTotals(); + + // bind any of the grid events + this._bindingEventService.bind(this.gridContainerElm, 'oncolumnsreordered', this.handleOnColumnsReordered.bind(this)); + this._bindingEventService.bind(this.gridContainerElm, 'oncellchange', this.handleOnCellChange.bind(this)); + + document.body.classList.add('salesforce-theme'); + } + + dispose() { + this._eventHandler.unsubscribeAll(); + this.sgb?.dispose(); + this.gridContainerElm.remove(); + document.querySelector('.demo-container')?.classList.remove('dark-mode'); + document.body.setAttribute('data-theme', 'light'); + } + + /* 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 } + }); + } + this.columnDefinitions = columnDefs; + + this.gridOptions = { + autoEdit: true, + autoCommitEdit: true, + editable: true, + darkMode: this._darkMode, + gridHeight: 450, + gridWidth: 800, + enableCellNavigation: true, + rowHeight: 33, + createFooterRow: true, + showFooterRow: true, + footerRowHeight: 35 + }; + } + + getData(itemCount: 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 < this.columnDefinitions.length; j++) { + d[j] = Math.round(Math.random() * 10); + } + } + + return datasetTmp; + } + + handleOnCellChange(e) { + const args = e?.detail?.args; + this.updateTotal(args.cell); + } + + handleOnColumnsReordered() { + this.updateAllTotals(); + } + + toggleDarkMode() { + this._darkMode = !this._darkMode; + this.toggleBodyBackground(); + this.sgb.gridOptions = { ...this.sgb.gridOptions, darkMode: this._darkMode }; + this.sgb.slickGrid?.setOptions({ darkMode: this._darkMode }); + this.updateAllTotals(); + } + + toggleBodyBackground() { + if (this._darkMode) { + document.body.setAttribute('data-theme', 'dark'); + document.querySelector('.demo-container')?.classList.add('dark-mode'); + } else { + document.body.setAttribute('data-theme', 'light'); + document.querySelector('.demo-container')?.classList.remove('dark-mode'); + } + } + + updateAllTotals() { + let columnIdx = this.sgb.slickGrid?.getColumns().length || 0; + while (columnIdx--) { + this.updateTotal(columnIdx); + } + } + + updateTotal(cell: number) { + const columnId = this.sgb.slickGrid?.getColumns()[cell].id as number; + + let total = 0; + let i = this.dataset.length; + while (i--) { + total += (parseInt(this.dataset[i][columnId], 10) || 0); + } + const columnElement = this.sgb.slickGrid?.getFooterRowColumn(columnId); + if (columnElement) { + columnElement.textContent = `Sum: ${total}`; + } + } +} diff --git a/examples/vite-demo-vanilla-bundle/src/styles.scss b/examples/vite-demo-vanilla-bundle/src/styles.scss index 012ce46ac..752a4790e 100644 --- a/examples/vite-demo-vanilla-bundle/src/styles.scss +++ b/examples/vite-demo-vanilla-bundle/src/styles.scss @@ -128,4 +128,7 @@ input.is-narrow { } .justify-center { justify-content: center; +} +.italic { + font-style: italic; } \ No newline at end of file diff --git a/packages/common/src/styles/_variables.scss b/packages/common/src/styles/_variables.scss index 2fbd984de..f1c887939 100644 --- a/packages/common/src/styles/_variables.scss +++ b/packages/common/src/styles/_variables.scss @@ -881,6 +881,16 @@ $slick-viewport-border-right: 0 none !default; $slick-viewport-border-bottom: 0 none !default; $slick-viewport-border-left: 0 none !default; +/* SlickGrid built-in Footer */ +$slick-grid-footer-display: flex !default; +$slick-grid-footer-align-items: center !default; +$slick-grid-footer-justify-content: normal !default; +$slick-grid-footer-top-border-top: 1px solid #d0d0d0 !default; +$slick-grid-footer-bg-color: #fafaf9 !default; +$slick-grid-footer-font-size: 13px !default; +$slick-grid-footer-font-style: normal !default; +$slick-grid-footer-font-weight: bold !default; + /* Custom Footer */ $slick-footer-bg-color: transparent !default; $slick-footer-font-size: $slick-font-size-base !default; @@ -1012,6 +1022,7 @@ $slick-dark-text-color: #d4d4d4; --slick-grid-menu-icon-btn-color: #ededed; --slick-row-mouse-hover-color: #2c3034; --slick-header-background-color: #1c1c1c; + --slick-grid-footer-bg-color: #1c1c1c; --slick-header-text-color: #e4e4e4; --slick-hover-header-color: var(--slick-base-dark-text-color); --slick-header-row-background-color: #2d2d2d; diff --git a/packages/common/src/styles/slick-grid.scss b/packages/common/src/styles/slick-grid.scss index 592f24dc6..5bb301c10 100644 --- a/packages/common/src/styles/slick-grid.scss +++ b/packages/common/src/styles/slick-grid.scss @@ -582,6 +582,20 @@ } } + .slick-footerrow { + border-top: var(--slick-grid-footer-top-border-top, $slick-grid-footer-top-border-top); + background-color: var(--slick-grid-footer-bg-color, $slick-grid-footer-bg-color); + .slick-footerrow-columns { + .slick-footerrow-column { + display: var(--slick-grid-footer-display, $slick-grid-footer-display); + align-items: var(--slick-grid-footer-align-items, $slick-grid-footer-align-items); + justify-content: var(--slick-grid-footer-justify-content, $slick-grid-footer-justify-content); + font-size: var(--slick-grid-footer-font-size, $slick-grid-footer-font-size); + font-style: var(--slick-grid-footer-font-style, $slick-grid-footer-font-style); + font-weight: var(--slick-grid-footer-font-weight, $slick-grid-footer-font-weight); + } + } + } .slick-header-columns { background: var(--slick-grid-header-background, $slick-grid-header-background); background-color: var(--slick-header-background-color, $slick-header-background-color); diff --git a/test/cypress/e2e/example24.cy.ts b/test/cypress/e2e/example24.cy.ts new file mode 100644 index 000000000..7c4274a2c --- /dev/null +++ b/test/cypress/e2e/example24.cy.ts @@ -0,0 +1,58 @@ +describe('Example 24 - 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')}/example24`); + cy.get('h3').should('contain', 'Example 24 - Footer Totals Row'); + cy.get('h3 span.subtitle').should('contain', '(with Salesforce Theme)'); + }); + + it('should have exact Column Header Titles in the grid', () => { + cy.get('.grid24') + .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); + }); + }); +});