diff --git a/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts index 02467604d..3106c35df 100644 --- a/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts @@ -3,7 +3,7 @@ import { Column, GridOption } from '../../interfaces/index'; import { GridMenuExtension } from '../gridMenuExtension'; import { ExtensionUtility } from '../extensionUtility'; import { SharedService } from '../../services/shared.service'; -import { /*ExcelExportService, ExportService,*/ FilterService, SortService } from '../../services'; +import { ExcelExportService, ExportService, FilterService, SortService } from '../../services'; import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; declare const Slick: any; @@ -13,13 +13,13 @@ const gridId = 'grid1'; const gridUid = 'slickgrid_124343'; const containerId = 'demo-container'; -// const excelExportServiceStub = { -// exportToExcel: jest.fn(), -// } as unknown as ExcelExportService; +const excelExportServiceStub = { + exportToExcel: jest.fn(), +} as unknown as ExcelExportService; -// const exportServiceStub = { -// exportToFile: jest.fn(), -// } as unknown as ExportService; +const exportServiceStub = { + exportToFile: jest.fn(), +} as unknown as ExportService; const filterServiceStub = { clearFilters: jest.fn(), @@ -120,7 +120,7 @@ describe('gridMenuExtension', () => { sharedService = new SharedService(); translateService = new TranslateServiceStub(); extensionUtility = new ExtensionUtility(sharedService, translateService); - extension = new GridMenuExtension(extensionUtility, filterServiceStub, sharedService, sortServiceStub, translateService); + extension = new GridMenuExtension(excelExportServiceStub, exportServiceStub, extensionUtility, filterServiceStub, sharedService, sortServiceStub, translateService); translateService.setLocale('fr'); }); @@ -574,51 +574,51 @@ describe('gridMenuExtension', () => { expect(refreshSpy).toHaveBeenCalled(); }); - // it('should call "exportToExcel" set when the command triggered is "export-excel"', () => { - // const excelExportSpy = jest.spyOn(excelExportServiceStub, 'exportToExcel'); - // const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); - - // const instance = extension.register(); - // instance.onCommand.notify({ grid: gridStub, command: 'export-excel' }, new Slick.EventData(), gridStub); - - // expect(onCommandSpy).toHaveBeenCalled(); - // expect(excelExportSpy).toHaveBeenCalledWith({ - // filename: 'export', - // format: FileType.xlsx, - // }); - // }); - - // it('should call "exportToFile" with CSV set when the command triggered is "export-csv"', () => { - // const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); - // const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); - - // const instance = extension.register(); - // instance.onCommand.notify({ grid: gridStub, command: 'export-csv' }, new Slick.EventData(), gridStub); - - // expect(onCommandSpy).toHaveBeenCalled(); - // expect(exportSpy).toHaveBeenCalledWith({ - // delimiter: DelimiterType.comma, - // filename: 'export', - // format: FileType.csv, - // useUtf8WithBom: true - // }); - // }); - - // it('should call "exportToFile" with CSV set when the command triggered is "export-text-delimited"', () => { - // const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); - // const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); - - // const instance = extension.register(); - // instance.onCommand.notify({ grid: gridStub, command: 'export-text-delimited' }, new Slick.EventData(), gridStub); - - // expect(onCommandSpy).toHaveBeenCalled(); - // expect(exportSpy).toHaveBeenCalledWith({ - // delimiter: DelimiterType.tab, - // filename: 'export', - // format: FileType.txt, - // useUtf8WithBom: true - // }); - // }); + it('should call "exportToExcel" set when the command triggered is "export-excel"', () => { + const excelExportSpy = jest.spyOn(excelExportServiceStub, 'exportToExcel'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'export-excel' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(excelExportSpy).toHaveBeenCalledWith({ + filename: 'export', + format: FileType.xlsx, + }); + }); + + it('should call "exportToFile" with CSV set when the command triggered is "export-csv"', () => { + const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'export-csv' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(exportSpy).toHaveBeenCalledWith({ + delimiter: DelimiterType.comma, + filename: 'export', + format: FileType.csv, + useUtf8WithBom: true + }); + }); + + it('should call "exportToFile" with CSV set when the command triggered is "export-text-delimited"', () => { + const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'export-text-delimited' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(exportSpy).toHaveBeenCalledWith({ + delimiter: DelimiterType.tab, + filename: 'export', + format: FileType.txt, + useUtf8WithBom: true + }); + }); it('should call the grid "setHeaderRowVisibility" method when the command triggered is "toggle-filter"', () => { gridOptionsMock.showHeaderRow = false; @@ -723,7 +723,7 @@ describe('gridMenuExtension', () => { describe('without Translate Service', () => { beforeEach(() => { translateService = null; - extension = new GridMenuExtension({} as ExtensionUtility, filterServiceStub, { gridOptions: { enableTranslate: true } } as SharedService, {} as SortService, translateService); + extension = new GridMenuExtension(excelExportServiceStub, exportServiceStub, {} as ExtensionUtility, filterServiceStub, { gridOptions: { enableTranslate: true } } as SharedService, {} as SortService, translateService); }); it('should throw an error if "enableTranslate" is set but the I18N Service is null', () => { diff --git a/packages/common/src/extensions/gridMenuExtension.ts b/packages/common/src/extensions/gridMenuExtension.ts index 3e333429a..355d63795 100644 --- a/packages/common/src/extensions/gridMenuExtension.ts +++ b/packages/common/src/extensions/gridMenuExtension.ts @@ -14,8 +14,8 @@ import { ExtensionName, FileType, } from '../enums/index'; -// import { ExcelExportService } from '../services/excelExport.service'; -// import { ExportService } from '../services/export.service'; +import { ExcelExportService } from '../services/excelExport.service'; +import { ExportService } from '../services/export.service'; import { ExtensionUtility } from './extensionUtility'; import { FilterService } from '../services/filter.service'; import { SortService } from '../services/sort.service'; @@ -35,8 +35,8 @@ export class GridMenuExtension implements Extension { private _userOriginalGridMenu: GridMenu; constructor( - // private excelExportService: ExcelExportService, - // private exportService: ExportService, + private excelExportService: ExcelExportService, + private exportService: ExportService, private extensionUtility: ExtensionUtility, private filterService: FilterService, private sharedService: SharedService, @@ -360,26 +360,26 @@ export class GridMenuExtension implements Extension { this.sharedService.dataView.refresh(); break; case 'export-csv': - // this.exportService.exportToFile({ - // delimiter: DelimiterType.comma, - // filename: 'export', - // format: FileType.csv, - // useUtf8WithBom: true, - // }); + this.exportService.exportToFile({ + delimiter: DelimiterType.comma, + filename: 'export', + format: FileType.csv, + useUtf8WithBom: true, + }); break; case 'export-excel': - // this.excelExportService.exportToExcel({ - // filename: 'export', - // format: FileType.xlsx, - // }); + this.excelExportService.exportToExcel({ + filename: 'export', + format: FileType.xlsx, + }); break; case 'export-text-delimited': - // this.exportService.exportToFile({ - // delimiter: DelimiterType.tab, - // filename: 'export', - // format: FileType.txt, - // useUtf8WithBom: true, - // }); + this.exportService.exportToFile({ + delimiter: DelimiterType.tab, + filename: 'export', + format: FileType.txt, + useUtf8WithBom: true, + }); break; case 'toggle-filter': const showHeaderRow = this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.showHeaderRow || false; diff --git a/packages/common/src/formatters/__tests__/treeFormatter.spec.ts b/packages/common/src/formatters/__tests__/treeFormatter.spec.ts new file mode 100644 index 000000000..fa028badd --- /dev/null +++ b/packages/common/src/formatters/__tests__/treeFormatter.spec.ts @@ -0,0 +1,95 @@ +import { Column } from '../../interfaces/index'; +import { treeFormatter } from '../treeFormatter'; + +const dataViewStub = { + getIdxById: jest.fn(), + getItemByIdx: jest.fn(), + getIdPropertyName: jest.fn(), +}; + +const gridStub = { + getData: jest.fn(), +}; + +describe('the Uppercase Formatter', () => { + let dataset; + + beforeEach(() => { + dataset = [ + { id: 0, firstName: 'John', lastName: 'Smith', email: 'john.smith@movie.com', parentId: null, indent: 0 }, + { id: 1, firstName: 'Jane', lastName: 'Doe', email: 'jane.doe@movie.com', parentId: 0, indent: 1 }, + { id: 2, firstName: 'Bob', lastName: 'Cane', email: 'bob.cane@movie.com', parentId: 1, indent: 2, __collapsed: true }, + { id: 2, firstName: 'Barbara', lastName: 'Cane', email: 'barbara.cane@movie.com', parentId: null, indent: 0, __collapsed: true }, + ]; + }); + + it('should throw an error when oarams are mmissing', () => { + expect(() => treeFormatter(1, 1, 'blah', {} as Column, {})) + .toThrowError('You must provide a valid Tree Data column, it seems that there are no tree level found in this row'); + }); + + it('should return empty string when DataView is not correctly formed', () => { + const output = treeFormatter(1, 1, '', { treeData: { levelPropName: 'indent' } } as Column, dataset[1]); + expect(output).toBe(''); + }); + + it('should return empty string when value is null', () => { + const output = treeFormatter(1, 1, null, { treeData: { levelPropName: 'indent' } } as Column, dataset[1]); + expect(output).toBe(''); + }); + + it('should return empty string when value is undefined', () => { + const output = treeFormatter(1, 1, undefined, { treeData: { levelPropName: 'indent' } } as Column, dataset[1]); + expect(output).toBe(''); + }); + + it('should return empty string when item is undefined', () => { + const output = treeFormatter(1, 1, 'blah', { treeData: { levelPropName: 'indent' } } as Column, undefined); + expect(output).toBe(''); + }); + + it('should return a span without any icon and ', () => { + jest.spyOn(gridStub, 'getData').mockReturnValue(dataViewStub); + jest.spyOn(dataViewStub, 'getIdxById').mockReturnValue(1); + jest.spyOn(dataViewStub, 'getItemByIdx').mockReturnValue(dataset[0]); + + const output = treeFormatter(1, 1, dataset[0]['firstName'], { treeData: { levelPropName: 'indent' } } as Column, dataset[0], gridStub); + expect(output).toBe(` John`); + }); + + it('should return a span without any icon and 15px indentation of a tree level 1', () => { + jest.spyOn(gridStub, 'getData').mockReturnValue(dataViewStub); + jest.spyOn(dataViewStub, 'getIdxById').mockReturnValue(1); + jest.spyOn(dataViewStub, 'getItemByIdx').mockReturnValue(dataset[1]); + + const output = treeFormatter(1, 1, dataset[1]['firstName'], { treeData: { levelPropName: 'indent' } } as Column, dataset[1], gridStub); + expect(output).toBe(` Jane`); + }); + + it('should return a span without any icon and 30px indentation of a tree level 2', () => { + jest.spyOn(gridStub, 'getData').mockReturnValue(dataViewStub); + jest.spyOn(dataViewStub, 'getIdxById').mockReturnValue(1); + jest.spyOn(dataViewStub, 'getItemByIdx').mockReturnValue(dataset[1]); + + const output = treeFormatter(1, 1, dataset[2]['firstName'], { treeData: { levelPropName: 'indent' } } as Column, dataset[2], gridStub); + expect(output).toBe(` Bob`); + }); + + it('should return a span with expanded icon and 15px indentation of a tree level 1 when current item is greater than next item', () => { + jest.spyOn(gridStub, 'getData').mockReturnValue(dataViewStub); + jest.spyOn(dataViewStub, 'getIdxById').mockReturnValue(1); + jest.spyOn(dataViewStub, 'getItemByIdx').mockReturnValue(dataset[2]); + + const output = treeFormatter(1, 1, dataset[1]['firstName'], { treeData: { levelPropName: 'indent' } } as Column, dataset[1], gridStub); + expect(output).toBe(` Jane`); + }); + + it('should return a span with collapsed icon and 0px indentation of a tree level 0 when current item is lower than next item', () => { + jest.spyOn(gridStub, 'getData').mockReturnValue(dataViewStub); + jest.spyOn(dataViewStub, 'getIdxById').mockReturnValue(1); + jest.spyOn(dataViewStub, 'getItemByIdx').mockReturnValue(dataset[1]); + + const output = treeFormatter(1, 1, dataset[3]['firstName'], { treeData: { levelPropName: 'indent' } } as Column, dataset[3], gridStub); + expect(output).toBe(` Barbara`); + }); +}); diff --git a/packages/common/src/formatters/treeFormatter.ts b/packages/common/src/formatters/treeFormatter.ts index c39e45a14..d51015764 100644 --- a/packages/common/src/formatters/treeFormatter.ts +++ b/packages/common/src/formatters/treeFormatter.ts @@ -5,12 +5,15 @@ export const treeFormatter: Formatter = (row: number, cell: number, value: any, const indentMarginLeft = columnDef.treeData?.indentMarginLeft || 15; const dataView = grid && grid.getData(); + if (value === null || value === undefined || dataContext === undefined) { + return ''; + } + if (!dataContext.hasOwnProperty(treeLevelPropName)) { throw new Error('You must provide a valid Tree Data column, it seems that there are no tree level found in this row'); } if (dataView && dataView.getIdxById && dataView.getItemByIdx) { - if (value === null || value === undefined || dataContext === undefined) { return ''; } value = value.replace(/&/g, '&').replace(//g, '>'); const identifierPropName = dataView.getIdPropertyName() || 'id'; const spacer = ``; diff --git a/packages/common/src/services/excelExport.service.ts b/packages/common/src/services/excelExport.service.ts new file mode 100644 index 000000000..b6149dbcb --- /dev/null +++ b/packages/common/src/services/excelExport.service.ts @@ -0,0 +1,21 @@ +import { ExcelExportOption } from '../interfaces/index'; + +export abstract class ExcelExportService { + /** + * Initialize the Export Service + * @param grid + * @param dataView + */ + init(grid: any, dataView: any): void { + console.log('ExcelExportService the "init" method must be implemented'); + } + + /** + * Method to return the current locale used by the App + * @return {string} current locale + */ + exportToExcel(options: ExcelExportOption): Promise { + console.log('ExcelExportService the "exportToExcel" method must be implemented'); + return new Promise((resolve) => resolve(true)); + } +} diff --git a/packages/common/src/services/export.service.ts b/packages/common/src/services/export.service.ts new file mode 100644 index 000000000..53ca03fd3 --- /dev/null +++ b/packages/common/src/services/export.service.ts @@ -0,0 +1,21 @@ +import { ExportOption } from '../interfaces/index'; + +export abstract class ExportService { + /** + * Initialize the Export Service + * @param grid + * @param dataView + */ + init(grid: any, dataView: any): void { + console.log('ExportService the "init" method must be implemented'); + } + + /** + * Method to return the current locale used by the App + * @return {string} current locale + */ + exportToFile(options: ExportOption): Promise { + console.log('ExportService the "exportToFile" method must be implemented'); + return new Promise((resolve) => resolve(true)); + } +} diff --git a/packages/common/src/services/index.ts b/packages/common/src/services/index.ts index 1cab5a3a5..f6256707a 100644 --- a/packages/common/src/services/index.ts +++ b/packages/common/src/services/index.ts @@ -1,5 +1,7 @@ export * from './collection.service'; +export * from './excelExport.service'; export * from './export-utilities'; +export * from './export.service'; export * from './extension.service'; export * from './filter.service'; export * from './grid.service'; @@ -8,5 +10,5 @@ export * from './groupingAndColspan.service'; export * from './pubSub.service'; export * from './shared.service'; export * from './sort.service'; -export * from './utilities'; export * from './translater.service'; +export * from './utilities'; diff --git a/packages/vanilla-bundle/src/services/excelExport.service.ts b/packages/vanilla-bundle/src/services/excelExport.service.ts new file mode 100644 index 000000000..0646cb848 --- /dev/null +++ b/packages/vanilla-bundle/src/services/excelExport.service.ts @@ -0,0 +1,11 @@ +import { ExcelExportService, ExcelExportOption } from '@slickgrid-universal/common'; + + +export class ExcelExportServicer implements ExcelExportService { + init(grid: any, dataView: any): void { + } + + exportToExcel(options: ExcelExportOption): Promise { + return new Promise((resolve) => resolve(true)); + } +} diff --git a/packages/vanilla-bundle/src/services/export.service.ts b/packages/vanilla-bundle/src/services/export.service.ts new file mode 100644 index 000000000..162a18aab --- /dev/null +++ b/packages/vanilla-bundle/src/services/export.service.ts @@ -0,0 +1,10 @@ +import { ExportService, ExportOption } from '@slickgrid-universal/common'; + +export class ExportServicer implements ExportService { + init(grid: any, dataView: any): void { + } + + exportToFile(options: ExportOption): Promise { + return new Promise((resolve) => resolve(true)); + } +} diff --git a/packages/vanilla-bundle/src/services/index.ts b/packages/vanilla-bundle/src/services/index.ts index 38769fbaf..1af0e36c8 100644 --- a/packages/vanilla-bundle/src/services/index.ts +++ b/packages/vanilla-bundle/src/services/index.ts @@ -1,2 +1,4 @@ export * from './eventPubSub.service'; +export * from './excelExport.service'; +export * from './export.service'; export * from './translate.service'; diff --git a/packages/vanilla-bundle/src/vanilla-grid-bundle.ts b/packages/vanilla-bundle/src/vanilla-grid-bundle.ts index 789b0227e..14ad07870 100644 --- a/packages/vanilla-bundle/src/vanilla-grid-bundle.ts +++ b/packages/vanilla-bundle/src/vanilla-grid-bundle.ts @@ -43,6 +43,8 @@ import { SlickgridConfig, } from '@slickgrid-universal/common'; +import { ExportServicer } from './services/export.service'; +import { ExcelExportServicer } from './services/excelExport.service'; import { TranslateService } from './services/translate.service'; import { EventPubSubService } from './services/eventPubSub.service'; @@ -79,6 +81,8 @@ export class VanillaGridBundle { columnPickerExtension: ColumnPickerExtension; checkboxExtension: CheckboxSelectorExtension; draggableGroupingExtension: DraggableGroupingExtension; + excelExportServicer: ExcelExportServicer; + exportServicer: ExportServicer; gridMenuExtension: GridMenuExtension; groupItemMetaProviderExtension: GroupItemMetaProviderExtension; headerButtonExtension: HeaderButtonExtension; @@ -119,6 +123,14 @@ export class VanillaGridBundle { this.refreshGridData(dataset); } + get datasetHierarchical(): any[] { + return this.sharedService.hierarchicalDataset; + } + + set datasetHierarchical(hierarchicalDataset: any[]) { + this.sharedService.hierarchicalDataset = hierarchicalDataset; + } + constructor(gridContainerElm: Element, columnDefs?: Column[], options?: GridOption, dataset?: any[]) { // make sure that the grid container has the "slickgrid-container" css class exist since we use it for slickgrid styling gridContainerElm.classList.add('slickgrid-container'); @@ -128,6 +140,8 @@ export class VanillaGridBundle { this.dataset = dataset || []; this._eventPubSubService = new EventPubSubService(gridContainerElm); + this.exportServicer = new ExportServicer(); + this.excelExportServicer = new ExcelExportServicer(); this.gridEventService = new GridEventService(); const slickgridConfig = new SlickgridConfig(); this.sharedService = new SharedService(); @@ -145,7 +159,7 @@ export class VanillaGridBundle { this.columnPickerExtension = new ColumnPickerExtension(this.extensionUtility, this.sharedService); this.checkboxExtension = new CheckboxSelectorExtension(this.extensionUtility, this.sharedService); this.draggableGroupingExtension = new DraggableGroupingExtension(this.extensionUtility, this.sharedService); - this.gridMenuExtension = new GridMenuExtension(this.extensionUtility, this.filterService, this.sharedService, this.sortService, this.translateService); + this.gridMenuExtension = new GridMenuExtension(this.excelExportServicer, this.exportServicer, this.extensionUtility, this.filterService, this.sharedService, this.sortService, this.translateService); this.groupItemMetaProviderExtension = new GroupItemMetaProviderExtension(this.sharedService); this.headerButtonExtension = new HeaderButtonExtension(this.extensionUtility, this.sharedService); this.headerMenuExtension = new HeaderMenuExtension(this.extensionUtility, this.filterService, this._eventPubSubService, this.sharedService, this.sortService, this.translateService);