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