Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(footer): add Footer Totals Row and fix footer styling #1576

Merged
merged 1 commit into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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' }
];
Expand Down
3 changes: 3 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ <h4 class="is-size-4 has-text-white">Slickgrid-Universal</h4>
<a class="navbar-item" onclick.delegate="loadRoute('example23')">
Example23 - Excel Export Formulas
</a>
<a class="navbar-item" onclick.delegate="loadRoute('example24')">
Example24 - Footer Totals Row
</a>
</div>
</div>
</div>
Expand Down
12 changes: 1 addition & 11 deletions examples/vite-demo-vanilla-bundle/src/examples/example20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export default class Example20 {
dataset: any[] = [];
gridOptions!: GridOption;
gridContainerElm: HTMLDivElement;
isWithPagination = true;
sgb: SlickVanillaGridBundle;

attached() {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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([]);
}
}
25 changes: 25 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example24.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<h3 class="title is-3">
Example 24 - Footer Totals Row
<span class="subtitle">(with Salesforce Theme)</span>
<span class="d-inline-flex">
<button class="button is-small" onclick.delegate="toggleDarkMode()" data-test="toggle-dark-mode">
<span class="mdi mdi-theme-light-dark"></span>
<span> Toggle Light/Dark</span>
</button>
</span>

<div class="subtitle code-link">
<span class="is-size-6">see</span>
<a class="is-size-5" target="_blank"
href="https://github.com/ghiscoding/slickgrid-universal/blob/master/examples/vite-demo-vanilla-bundle/src/examples/example24.ts">
<span class="mdi mdi-link-variant"></span> code
</a>
</div>
</h3>

<h6 class="title is-6 italic">
Display a totals row at the end of the grid.
</h6>

<div class="grid24">
</div>
143 changes: 143 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example24.ts
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>('.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}`;
}
}
}
3 changes: 3 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,7 @@ input.is-narrow {
}
.justify-center {
justify-content: center;
}
.italic {
font-style: italic;
}
11 changes: 11 additions & 0 deletions packages/common/src/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions packages/common/src/styles/slick-grid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
58 changes: 58 additions & 0 deletions test/cypress/e2e/example24.cy.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
Loading