diff --git a/packages/common/src/interfaces/customFooterOption.interface.ts b/packages/common/src/interfaces/customFooterOption.interface.ts index daf1e5d65..cffbcfba2 100644 --- a/packages/common/src/interfaces/customFooterOption.interface.ts +++ b/packages/common/src/interfaces/customFooterOption.interface.ts @@ -1,7 +1,7 @@ import { MetricTexts } from './metricTexts.interface'; export interface CustomFooterOption { - /** Optionally pass some text to be displayed on the left side (in the "left-footer" css class) */ + /** Optionally provide some text to be displayed on the left side of the footer (in the "left-footer" css class) */ leftFooterText?: string; /** CSS class used for the left container */ @@ -39,4 +39,7 @@ export interface CustomFooterOption { /** CSS class used for the right container */ rightContainerClass?: string; + + /** Optionally provide some text to be displayed on the right side of the footer (in the "right-footer" css class) */ + rightFooterText?: string; } diff --git a/packages/common/src/styles/slick-component.scss b/packages/common/src/styles/slick-component.scss index 8ae6d25b7..c708f9fd5 100644 --- a/packages/common/src/styles/slick-component.scss +++ b/packages/common/src/styles/slick-component.scss @@ -24,7 +24,7 @@ float: $footer-left-float; } - .right-footer.metrics { + .right-footer { color: $footer-right-text-color; text-align: $footer-right-text-align; font-style: $footer-right-font-style; @@ -33,7 +33,7 @@ padding: $footer-right-padding; width: $footer-right-width; float: $footer-right-float; - .separator { + &.metrics .separator { margin: $footer-right-separator-margin; } } diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts index 08150e1b2..1eea3feed 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts @@ -1,3 +1,4 @@ +import 'jest-extended'; import { CustomFooterOption, GridOption, SlickGrid } from '@slickgrid-universal/common'; import { SlickFooterComponent } from '../slick-footer.component'; import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; @@ -67,7 +68,7 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(translateService.getCurrentLanguage()).toBe('en'); expect(footerContainerElm).toBeTruthy(); @@ -85,7 +86,7 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer') as HTMLSpanElement; expect(footerContainerElm).toBeTruthy(); expect(leftFooterElm).toBeTruthy(); @@ -101,18 +102,18 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(footerContainerElm).toBeTruthy(); expect(leftFooterElm).toBeTruthy(); expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + 7some of99 + some items`)); }); it('should create a the Slick-Footer component in the DOM with metrics but without timestamp when hidding it', () => { @@ -125,17 +126,16 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(footerContainerElm).toBeTruthy(); expect(leftFooterElm).toBeTruthy(); expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + ` + 7some of99 + some items`)); }); it('should create a the Slick-Footer component in the DOM with metrics but without timestamp neither totalCount when hidding it', () => { @@ -149,17 +149,16 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(footerContainerElm).toBeTruthy(); expect(leftFooterElm).toBeTruthy(); expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + ` + 7 + some items`)); }); it('should create a the Slick-Footer component in the DOM and expect to use default English locale when none of the metricsText are defined', () => { @@ -171,18 +170,18 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(footerContainerElm).toBeTruthy(); expect(leftFooterElm).toBeTruthy(); expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + 7of99 + items`)); }); it('should create a the Slick-Footer component in the DOM and use different locale when enableTranslate is enabled', () => { @@ -196,17 +195,34 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(footerContainerElm).toBeTruthy(); expect(leftFooterElm).toBeTruthy(); expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + `Dernière mise à jour + 2019-05-03, 12:00:01am | + 7de99 + éléments`)); + }); + + it('should read initial custom left text from grid options and display it on the left side footer section when calling the leftFooterText SETTER', () => { + mockGridOptions.enableCheckboxSelector = true; + const customFooterOptions = mockGridOptions.customFooterOptions as CustomFooterOption; + customFooterOptions.leftFooterText = 'initial left footer text'; + component = new SlickFooterComponent(gridStub, customFooterOptions, translateService); + component.renderFooter(div); + component.metrics = { startTime: mockTimestamp, endTime: mockTimestamp, itemCount: 7, totalItemCount: 99 }; + + const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; + const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; + + expect(component.eventHandler).toEqual(expect.toBeObject()); + expect(footerContainerElm).toBeTruthy(); + expect(leftFooterElm).toBeTruthy(); + expect(leftFooterElm.innerHTML).toBe('initial left footer text'); }); it('should display custom text on the left side footer section when calling the leftFooterText SETTER', () => { @@ -218,7 +234,7 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(component.eventHandler).toEqual(expect.toBeObject()); expect(footerContainerElm).toBeTruthy(); @@ -226,10 +242,9 @@ describe('Slick-Footer Component', () => { expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe('custom left footer text'); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + `some last update2019-05-03, 12:00:01am | + 7some of99 + some items`)); }); it('should display 1 items selected on the left side footer section after triggering "onSelectedRowsChanged" event', () => { @@ -241,7 +256,7 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(component.eventHandler).toEqual(expect.toBeObject()); expect(footerContainerElm).toBeTruthy(); @@ -249,11 +264,10 @@ describe('Slick-Footer Component', () => { expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe('1 items selected'); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + 7some of99 + some items`)); gridStub.onSelectedRowsChanged.notify({ rows: [1, 2, 3, 4, 5], grid: gridStub, previousSelectedRows: [] }); expect(leftFooterElm.innerHTML).toBe('5 items selected'); @@ -269,7 +283,7 @@ describe('Slick-Footer Component', () => { const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer') as HTMLSpanElement; - const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics') as HTMLSpanElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer.metrics') as HTMLSpanElement; expect(component.eventHandler).toEqual(expect.toBeObject()); expect(footerContainerElm).toBeTruthy(); @@ -277,10 +291,10 @@ describe('Slick-Footer Component', () => { expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - ``)); + 7some of99 + some items`)); }); it('should display row selection count on the left side footer section after triggering "onSelectedRowsChanged" event', () => { @@ -318,5 +332,38 @@ describe('Slick-Footer Component', () => { gridStub.onSelectedRowsChanged.notify({ rows: [1], previousSelectedRows: [], grid: gridStub, }); expect(component.leftFooterText).toBe('0 items selected'); }); + + it('should read initial custom right text from grid options and display it on the right side footer section when calling the rightFooterText SETTER', () => { + mockGridOptions.enableCheckboxSelector = true; + const customFooterOptions = mockGridOptions.customFooterOptions as CustomFooterOption; + customFooterOptions.rightFooterText = 'initial right footer text'; + component = new SlickFooterComponent(gridStub, customFooterOptions, translateService); + component.renderFooter(div); + component.metrics = { startTime: mockTimestamp, endTime: mockTimestamp, itemCount: 7, totalItemCount: 99 }; + + const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer') as HTMLSpanElement; + + expect(component.eventHandler).toEqual(expect.toBeObject()); + expect(footerContainerElm).toBeTruthy(); + expect(rightFooterElm).toBeTruthy(); + expect(rightFooterElm.innerHTML).toBe('initial right footer text'); + }); + + it('should display custom text on the right side footer section when calling the rightFooterText SETTER', () => { + mockGridOptions.enableCheckboxSelector = true; + component = new SlickFooterComponent(gridStub, mockGridOptions.customFooterOptions as CustomFooterOption, translateService); + component.renderFooter(div); + component.metrics = { startTime: mockTimestamp, endTime: mockTimestamp, itemCount: 7, totalItemCount: 99 }; + component.rightFooterText = 'custom right footer text'; + + const footerContainerElm = document.querySelector('div.slick-custom-footer.slickgrid_123456') as HTMLDivElement; + const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.right-footer') as HTMLSpanElement; + + expect(component.eventHandler).toEqual(expect.toBeObject()); + expect(footerContainerElm).toBeTruthy(); + expect(rightFooterElm).toBeTruthy(); + expect(rightFooterElm.innerHTML).toBe('custom right footer text'); + }); }); }); diff --git a/packages/vanilla-bundle/src/components/slick-footer.component.ts b/packages/vanilla-bundle/src/components/slick-footer.component.ts index 206721b30..1f8ce2eb4 100644 --- a/packages/vanilla-bundle/src/components/slick-footer.component.ts +++ b/packages/vanilla-bundle/src/components/slick-footer.component.ts @@ -24,6 +24,7 @@ export class SlickFooterComponent { private _footerElement!: HTMLDivElement; private _isLeftFooterOriginallyEmpty = true; private _isLeftFooterDisplayingSelectionRowCount = false; + private _isRightFooterOriginallyEmpty = true; private _selectedRowCount = 0; get eventHandler(): SlickEventHandler { @@ -55,11 +56,19 @@ export class SlickFooterComponent { this.renderLeftFooterText(text); } + get rightFooterText(): string { + return document.querySelector('div.right-footer')?.textContent ?? ''; + } + set rightFooterText(text: string) { + this.renderRightFooterText(text); + } + constructor(private grid: SlickGrid, private customFooterOptions: CustomFooterOption, private translaterService?: TranslaterService) { this._bindingHelper = new BindingHelper(); this._bindingHelper.querySelectorPrefix = `.${this.gridUid} `; this._eventHandler = new Slick.EventHandler(); this._isLeftFooterOriginallyEmpty = !(this.gridOptions.customFooterOptions?.leftFooterText); + this._isRightFooterOriginallyEmpty = !(this.gridOptions.customFooterOptions?.rightFooterText); this.registerOnSelectedRowsChangedWhenEnabled(customFooterOptions); } @@ -109,6 +118,11 @@ export class SlickFooterComponent { this._bindingHelper.setElementAttributeValue('div.left-footer', 'textContent', text); } + /** Render the right side footer text */ + renderRightFooterText(text: string) { + this._bindingHelper.setElementAttributeValue('div.right-footer', 'textContent', text); + } + // -- // private functions // -------------------- @@ -124,18 +138,10 @@ export class SlickFooterComponent { leftFooterElm.className = `left-footer ${this.customFooterOptions.leftContainerClass}`; leftFooterElm.innerHTML = sanitizeTextByAvailableSanitizer(this.gridOptions, this.customFooterOptions.leftFooterText || ''); - const metricsElm = document.createElement('div'); - metricsElm.className = 'metrics'; - - if (!this.customFooterOptions.hideMetrics) { - const rightFooterElm = this.createFooterRightContainer(); - if (rightFooterElm) { - metricsElm.appendChild(rightFooterElm); - } - } + const rightFooterElm = this.createFooterRightContainer(); footerElm.appendChild(leftFooterElm); - footerElm.appendChild(metricsElm); + footerElm.appendChild(rightFooterElm); this._footerElement = footerElm; if (gridParentContainerElm?.appendChild && this._footerElement) { @@ -146,53 +152,58 @@ export class SlickFooterComponent { /** Create the Right Section Footer */ private createFooterRightContainer(): HTMLDivElement { const rightFooterElm = document.createElement('div'); - rightFooterElm.className = `right-footer metrics ${this.customFooterOptions.rightContainerClass || ''}`; - - const lastUpdateElm = document.createElement('span'); - lastUpdateElm.className = 'timestamp'; - - if (!this.customFooterOptions.hideLastUpdateTimestamp) { - const footerLastUpdateElm = this.createFooterLastUpdate(); - if (footerLastUpdateElm) { - lastUpdateElm.appendChild(footerLastUpdateElm); + rightFooterElm.className = `right-footer ${this.customFooterOptions.rightContainerClass || ''}`; + + if (!this._isRightFooterOriginallyEmpty) { + rightFooterElm.innerHTML = sanitizeTextByAvailableSanitizer(this.gridOptions, this.customFooterOptions.rightFooterText || ''); + } else if (!this.customFooterOptions.hideMetrics) { + rightFooterElm.classList.add('metrics'); + const lastUpdateElm = document.createElement('span'); + lastUpdateElm.className = 'timestamp'; + + if (!this.customFooterOptions.hideLastUpdateTimestamp) { + const footerLastUpdateElm = this.createFooterLastUpdate(); + if (footerLastUpdateElm) { + lastUpdateElm.appendChild(footerLastUpdateElm); + } } - } - const itemCountElm = document.createElement('span'); - itemCountElm.className = 'item-count'; - itemCountElm.textContent = `${this.metrics?.itemCount ?? '0'}`; + const itemCountElm = document.createElement('span'); + itemCountElm.className = 'item-count'; + itemCountElm.textContent = `${this.metrics?.itemCount ?? '0'}`; - // last update elements - rightFooterElm.appendChild(lastUpdateElm); - rightFooterElm.appendChild(itemCountElm); + // last update elements + rightFooterElm.appendChild(lastUpdateElm); + rightFooterElm.appendChild(itemCountElm); - // total count element (unless hidden) - if (!this.customFooterOptions.hideTotalItemCount) { - // add carriage return which will add a space before the span - rightFooterElm.appendChild(document.createTextNode('\r\n')); + // total count element (unless hidden) + if (!this.customFooterOptions.hideTotalItemCount) { + // add carriage return which will add a space before the span + rightFooterElm.appendChild(document.createTextNode('\r\n')); - const textOfElm = document.createElement('span'); - textOfElm.className = 'text-of'; - textOfElm.textContent = ` ${this.customFooterOptions.metricTexts?.of ?? 'of'} `; - rightFooterElm.appendChild(textOfElm); + const textOfElm = document.createElement('span'); + textOfElm.className = 'text-of'; + textOfElm.textContent = ` ${this.customFooterOptions.metricTexts?.of ?? 'of'} `; + rightFooterElm.appendChild(textOfElm); - // add another carriage return which will add a space after the span - rightFooterElm.appendChild(document.createTextNode('\r\n')); + // add another carriage return which will add a space after the span + rightFooterElm.appendChild(document.createTextNode('\r\n')); - const totalCountElm = document.createElement('span'); - totalCountElm.className = 'total-count'; - totalCountElm.textContent = `${this.metrics?.totalItemCount ?? '0'}`; + const totalCountElm = document.createElement('span'); + totalCountElm.className = 'total-count'; + totalCountElm.textContent = `${this.metrics?.totalItemCount ?? '0'}`; - rightFooterElm.appendChild(totalCountElm); - } + rightFooterElm.appendChild(totalCountElm); + } - // add carriage return which will add a space before the span - rightFooterElm.appendChild(document.createTextNode('\r\n')); + // add carriage return which will add a space before the span + rightFooterElm.appendChild(document.createTextNode('\r\n')); - const textItemsElm = document.createElement('span'); - textItemsElm.className = 'text-items'; - textItemsElm.textContent = ` ${this.customFooterOptions.metricTexts?.items ?? 'items'} `; - rightFooterElm.appendChild(textItemsElm); + const textItemsElm = document.createElement('span'); + textItemsElm.className = 'text-items'; + textItemsElm.textContent = ` ${this.customFooterOptions.metricTexts?.items ?? 'items'} `; + rightFooterElm.appendChild(textItemsElm); + } return rightFooterElm; }