Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit 003738c

Browse files
authored
Merge pull request #730 from ghiscoding/bugfix/export-colspan
fix(exports): grid with colspan should be export accordingly
2 parents c1fdde9 + c282dff commit 003738c

File tree

8 files changed

+396
-151
lines changed

8 files changed

+396
-151
lines changed

src/app/examples/grid-colspan.component.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class GridColspanComponent implements OnInit {
5454
this.gridOptions1 = {
5555
enableAutoResize: false,
5656
enableCellNavigation: true,
57+
enableExport: true,
5758
enableSorting: true,
5859
createPreHeaderPanel: true,
5960
showPreHeaderPanel: true,
@@ -127,14 +128,13 @@ export class GridColspanComponent implements OnInit {
127128
}
128129
}
129130
};
130-
} else {
131-
return {
132-
columns: {
133-
0: {
134-
colspan: '*' // starting at column index 0, we will span accross all column (*)
135-
}
136-
}
137-
};
138131
}
132+
return {
133+
columns: {
134+
0: {
135+
colspan: '*' // starting at column index 0, we will span accross all column (*)
136+
}
137+
}
138+
};
139139
}
140140
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as entry from './index';
2+
3+
describe('Testing library entry point', () => {
4+
it('should have an index entry point defined', () => {
5+
expect(entry).toBeTruthy();
6+
});
7+
8+
it('should have all exported object defined', () => {
9+
expect(typeof entry.AngularSlickgridComponent).toBe('function');
10+
expect(typeof entry.AngularSlickgridModule).toBe('function');
11+
expect(typeof entry.SlickgridConfig).toBe('function');
12+
expect(typeof entry.SlickPaginationComponent).toBe('function');
13+
});
14+
});

src/app/modules/angular-slickgrid/services/__tests__/excelExport.service.spec.ts

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import * as moment from 'moment-mini';
44

55
import {
66
Column,
7+
ExcelExportOption,
8+
FieldType,
79
FileType,
810
Formatter,
911
GridOption,
10-
FieldType,
1112
GroupTotalsFormatter,
13+
ItemMetadata,
1214
SortDirectionNumber,
13-
ExcelExportOption,
1415
} from '../../models';
1516
import { ExcelExportService } from '../excelExport.service';
1617
import { Formatters } from '../../formatters/index';
@@ -20,8 +21,8 @@ import { GroupTotalFormatters } from '../..';
2021
// URL object is not supported in JSDOM, we can simply mock it
2122
(global as any).URL.createObjectURL = jest.fn();
2223

23-
const myBoldHtmlFormatter: Formatter = (row, cell, value, columnDef, dataContext) => value !== null ? { text: `<b>${value}</b>` } : null;
24-
const myUppercaseFormatter: Formatter = (row, cell, value, columnDef, dataContext) => value ? { text: value.toUpperCase() } : null;
24+
const myBoldHtmlFormatter: Formatter = (row, cell, value) => value !== null ? { text: `<b>${value}</b>` } : '';
25+
const myUppercaseFormatter: Formatter = (row, cell, value) => value ? { text: value.toUpperCase() } : '';
2526
const myUppercaseGroupTotalFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column) => {
2627
const field = columnDef.field || '';
2728
const val = totals.sum && totals.sum[field];
@@ -44,6 +45,7 @@ const myCustomObjectFormatter: Formatter = (row: number, cell: number, value: an
4445
const dataViewStub = {
4546
getGrouping: jest.fn(),
4647
getItem: jest.fn(),
48+
getItemMetadata: jest.fn(),
4749
getLength: jest.fn(),
4850
setGrouping: jest.fn(),
4951
};
@@ -58,6 +60,7 @@ const gridStub = {
5860
getColumnIndex: jest.fn(),
5961
getOptions: () => mockGridOptions,
6062
getColumns: jest.fn(),
63+
getData: () => dataViewStub,
6164
getGrouping: jest.fn(),
6265
};
6366

@@ -72,7 +75,7 @@ describe('ExcelExportService', () => {
7275
beforeEach(() => {
7376
// @ts-ignore
7477
navigator.__defineGetter__('appName', () => 'Netscape');
75-
navigator.msSaveOrOpenBlob = undefined;
78+
navigator.msSaveOrOpenBlob = undefined as any;
7679
mockExcelBlob = new Blob(['', ''], { type: `text/xlsx;charset=utf-8;` });
7780

7881
mockExportExcelOptions = {
@@ -535,7 +538,6 @@ describe('ExcelExportService', () => {
535538
jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
536539
});
537540

538-
539541
it(`should expect Date exported correctly when Field Type is provided and we use "exportWithFormatter" set to True & False`, async () => {
540542
mockCollection = [
541543
{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', startDate: '2005-12-20T18:19:19.992Z', endDate: null },
@@ -696,10 +698,10 @@ describe('ExcelExportService', () => {
696698
aggregateEmpty: false,
697699
aggregators: [{ _count: 2, _field: 'order', _nonNullCount: 2, _sum: 4, }],
698700
collapsed: false,
699-
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
701+
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
700702
compiledAccumulators: [jest.fn(), jest.fn()],
701703
displayTotalsRow: true,
702-
formatter: (g) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
704+
formatter: (g: any) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
703705
getter: 'order',
704706
getterIsAFn: false,
705707
lazyTotalsCalculation: true,
@@ -790,10 +792,10 @@ describe('ExcelExportService', () => {
790792
aggregateEmpty: false,
791793
aggregators: [{ _count: 2, _field: 'order', _nonNullCount: 2, _sum: 4, }],
792794
collapsed: false,
793-
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
795+
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
794796
compiledAccumulators: [jest.fn(), jest.fn()],
795797
displayTotalsRow: true,
796-
formatter: (g) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
798+
formatter: (g: any) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
797799
getter: 'order',
798800
getterIsAFn: false,
799801
lazyTotalsCalculation: true,
@@ -888,10 +890,10 @@ describe('ExcelExportService', () => {
888890
aggregateEmpty: false,
889891
aggregators: [{ _count: 2, _field: 'order', _nonNullCount: 2, _sum: 4, }],
890892
collapsed: false,
891-
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
893+
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
892894
compiledAccumulators: [jest.fn(), jest.fn()],
893895
displayTotalsRow: true,
894-
formatter: (g) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
896+
formatter: (g: any) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
895897
getter: 'order',
896898
getterIsAFn: false,
897899
lazyTotalsCalculation: true,
@@ -904,10 +906,10 @@ describe('ExcelExportService', () => {
904906
aggregateEmpty: false,
905907
aggregators: [{ _count: 1, _field: 'lastName', _nonNullCount: 2, _sum: 4, }],
906908
collapsed: false,
907-
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
909+
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
908910
compiledAccumulators: [jest.fn(), jest.fn()],
909911
displayTotalsRow: true,
910-
formatter: (g) => `Last Name: ${g.value} <span style="color:green">(${g.count} items)</span>`,
912+
formatter: (g: any) => `Last Name: ${g.value} <span style="color:green">(${g.count} items)</span>`,
911913
getter: 'lastName',
912914
getterIsAFn: false,
913915
lazyTotalsCalculation: true,
@@ -998,7 +1000,7 @@ describe('ExcelExportService', () => {
9981000
it(`should have a xlsx export with grouping but without indentation when "addGroupIndentation" is set to False
9991001
and field should be exported as metadata when "exportWithFormatter" is false and the field type is number`, async () => {
10001002
mockColumns[5].exportWithFormatter = false; // "order" field that is of type number will be exported as a number cell format metadata
1001-
mockGridOptions.excelExportOptions.addGroupIndentation = false;
1003+
mockGridOptions.excelExportOptions!.addGroupIndentation = false;
10021004
const spyOnAfter = jest.spyOn(service.onGridAfterExportToExcel, 'next');
10031005
const spyUrlCreate = jest.spyOn(URL, 'createObjectURL');
10041006
const spyDownload = jest.spyOn(service, 'startDownloadFile');
@@ -1022,9 +1024,9 @@ describe('ExcelExportService', () => {
10221024
],
10231025
['Order: 20 (2 items)'],
10241026
['Last Name: Z (1 items)'],
1025-
['', '1E06', 'John', 'Z', 'Sales Rep.', { metadata: { style: 3 }, value: '10', }],
1027+
['', '1E06', 'John', 'Z', 'Sales Rep.', { metadata: { style: 3, type: 'number' }, value: 10, }],
10261028
['Last Name: Doe (1 items)'],
1027-
['', '2B02', 'Jane', 'DOE', 'Finance Manager', { metadata: { style: 3 }, value: '10', }],
1029+
['', '2B02', 'Jane', 'DOE', 'Finance Manager', { metadata: { style: 3, type: 'number' }, value: 10, }],
10281030
['Last Name: null (0 items)'],
10291031
['', '', '', '', '', '20'],
10301032
['', '', '', '', '', '10'],
@@ -1367,7 +1369,7 @@ describe('ExcelExportService', () => {
13671369
});
13681370

13691371
it(`should have the LastName header title translated when defined as a "headerKey" and "i18n" is set in grid option`, async () => {
1370-
mockGridOptions.excelExportOptions.sanitizeDataExport = false;
1372+
mockGridOptions.excelExportOptions!.sanitizeDataExport = false;
13711373
mockCollection2 = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }];
13721374
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection2.length);
13731375
jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection2[0]);
@@ -1404,6 +1406,83 @@ describe('ExcelExportService', () => {
14041406
});
14051407
});
14061408
});
1409+
1410+
describe('grid with colspan', () => {
1411+
let mockCollection;
1412+
let oddMetatadata = { columns: { lastName: { colspan: 2 } } } as ItemMetadata;
1413+
let evenMetatadata = { columns: { 0: { colspan: '*' } } } as ItemMetadata;
1414+
1415+
beforeEach(() => {
1416+
mockGridOptions.enableTranslate = true;
1417+
mockGridOptions.i18n = translate;
1418+
mockGridOptions.excelExportOptions = {};
1419+
mockGridOptions.createPreHeaderPanel = false;
1420+
mockGridOptions.showPreHeaderPanel = false;
1421+
mockGridOptions.colspanCallback = (item: any) => (item.id % 2 === 1) ? evenMetatadata : oddMetatadata;
1422+
1423+
mockColumns = [
1424+
{ id: 'userId', field: 'userId', name: 'User Id', width: 100 },
1425+
{ id: 'firstName', nameKey: 'FIRST_NAME', width: 100, formatter: myBoldHtmlFormatter },
1426+
{ id: 'lastName', field: 'lastName', nameKey: 'LAST_NAME', width: 100, formatter: myBoldHtmlFormatter, exportCustomFormatter: myUppercaseFormatter, sanitizeDataExport: true, exportWithFormatter: true },
1427+
{ id: 'position', field: 'position', name: 'Position', width: 100, formatter: Formatters.translate, exportWithFormatter: true },
1428+
{ id: 'order', field: 'order', width: 100, },
1429+
] as Column[];
1430+
1431+
jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
1432+
});
1433+
1434+
afterEach(() => {
1435+
jest.clearAllMocks();
1436+
});
1437+
1438+
it('should return associated Excel column name when calling "getExcelColumnNameByIndex" method with a column index', () => {
1439+
const excelColumnA = service.getExcelColumnNameByIndex(1);
1440+
const excelColumnZ = service.getExcelColumnNameByIndex(26);
1441+
const excelColumnAA = service.getExcelColumnNameByIndex(27);
1442+
const excelColumnCA = service.getExcelColumnNameByIndex(79);
1443+
1444+
expect(excelColumnA).toBe('A');
1445+
expect(excelColumnZ).toBe('Z');
1446+
expect(excelColumnAA).toBe('AA');
1447+
expect(excelColumnCA).toBe('CA');
1448+
});
1449+
1450+
it(`should export same colspan in the export excel as defined in the grid`, async () => {
1451+
mockCollection = [
1452+
{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
1453+
{ id: 1, userId: '1E09', firstName: 'Jane', lastName: 'Doe', position: 'DEVELOPER', order: 15 },
1454+
{ id: 2, userId: '2ABC', firstName: 'Sponge', lastName: 'Bob', position: 'IT_ADMIN', order: 33 },
1455+
];
1456+
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length);
1457+
jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]).mockReturnValueOnce(mockCollection[1]).mockReturnValueOnce(mockCollection[2]);
1458+
jest.spyOn(dataViewStub, 'getItemMetadata').mockReturnValue(oddMetatadata).mockReturnValueOnce(evenMetatadata).mockReturnValueOnce(oddMetatadata).mockReturnValueOnce(evenMetatadata);
1459+
const spyOnAfter = jest.spyOn(service.onGridAfterExportToExcel, 'next');
1460+
const spyUrlCreate = jest.spyOn(URL, 'createObjectURL');
1461+
const spyDownload = jest.spyOn(service, 'startDownloadFile');
1462+
1463+
const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx };
1464+
1465+
service.init(gridStub, dataViewStub);
1466+
await service.exportToExcel(mockExportExcelOptions);
1467+
1468+
expect(spyOnAfter).toHaveBeenCalledWith(optionExpectation);
1469+
expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob);
1470+
expect(spyDownload).toHaveBeenCalledWith({
1471+
...optionExpectation, blob: new Blob(), data: [
1472+
[
1473+
{ metadata: { style: 1, }, value: 'User Id', },
1474+
{ metadata: { style: 1, }, value: 'First Name', },
1475+
{ metadata: { style: 1, }, value: 'Last Name', },
1476+
{ metadata: { style: 1, }, value: 'Position', },
1477+
{ metadata: { style: 1, }, value: 'Order', },
1478+
],
1479+
['1E06', '', '', ''],
1480+
['1E09', 'Jane', 'DOE', '', 15],
1481+
['2ABC', '', '', ''],
1482+
]
1483+
});
1484+
});
1485+
});
14071486
});
14081487

14091488
describe('without ngx-translate', () => {

src/app/modules/angular-slickgrid/services/__tests__/export-utilities.spec.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import { exportWithFormatterWhenDefined } from '../export-utilities';
22
import { Column, Formatter, SlickGrid } from '../../models/index';
33

4+
const mockDataView = {
5+
constructor: jest.fn(),
6+
init: jest.fn(),
7+
destroy: jest.fn(),
8+
getItemMetadata: jest.fn(),
9+
};
10+
11+
const gridStub = {
12+
getData: () => mockDataView,
13+
};
14+
415
describe('Export Utilities', () => {
5-
let mockItem;
16+
let mockItem: any;
617
let mockColumn: Column;
718
const myBoldHtmlFormatter: Formatter = (_row, _cell, value) => value !== null ? { text: value ? `<b>${value}</b>` : '' } : null as any;
819
const myUppercaseFormatter: Formatter = (_row, _cell, value) => value ? { text: value.toUpperCase() } : null as any;
@@ -14,65 +25,65 @@ describe('Export Utilities', () => {
1425

1526
describe('exportWithFormatterWhenDefined method', () => {
1627
it('should NOT enable exportWithFormatter and expect the firstName to returned', () => {
17-
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, {} as SlickGrid, { exportWithFormatter: false });
28+
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, gridStub as SlickGrid, { exportWithFormatter: false });
1829
expect(output).toBe('John');
1930
});
2031

2132
it('should provide a column definition field defined with a dot (.) notation and expect a complex object result', () => {
22-
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'address.zip' }, {} as SlickGrid, {});
33+
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'address.zip' }, gridStub as SlickGrid, {});
2334
expect(output).toEqual({ zip: 12345 });
2435
});
2536

2637
it('should provide a column definition field defined with a dot (.) notation and expect an empty string when the complex result is an empty object', () => {
27-
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, {} as SlickGrid, {});
38+
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, gridStub as SlickGrid, {});
2839
expect(output).toEqual('');
2940
});
3041

3142
it('should provide a column definition field defined with a dot (.) notation and expect an empty string when the complex result is an empty object', () => {
32-
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, {} as SlickGrid, {});
43+
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, gridStub as SlickGrid, {});
3344
expect(output).toEqual('');
3445
});
3546

3647
it('should provide a exportCustomFormatter in the column definition and expect the output to be formatted', () => {
37-
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, {} as SlickGrid, { exportWithFormatter: true });
48+
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, gridStub as SlickGrid, { exportWithFormatter: true });
3849
expect(output).toBe('<b>John</b>');
3950
});
4051

4152
it('should provide a exportCustomFormatter in the column definition and expect empty string when associated item property is null', () => {
42-
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, {} as SlickGrid, { exportWithFormatter: true });
53+
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, gridStub as SlickGrid, { exportWithFormatter: true });
4354
expect(output).toBe('');
4455
});
4556

4657
it('should provide a exportCustomFormatter in the column definition and expect empty string when associated item property is undefined', () => {
47-
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, {} as SlickGrid, { exportWithFormatter: true });
58+
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, gridStub as SlickGrid, { exportWithFormatter: true });
4859
expect(output).toBe('');
4960
});
5061

5162
it('should enable exportWithFormatter as an exportOption and expect the firstName to be formatted', () => {
52-
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
63+
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
5364
expect(output).toBe('JOHN');
5465
});
5566

5667
it('should enable exportWithFormatter as a grid option and expect the firstName to be formatted', () => {
5768
mockColumn.exportWithFormatter = true;
58-
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
69+
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
5970
expect(output).toBe('JOHN');
6071
});
6172

6273
it('should enable exportWithFormatter as a grid option and expect empty string when associated item property is null', () => {
6374
mockColumn.exportWithFormatter = true;
64-
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
75+
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
6576
expect(output).toBe('');
6677
});
6778

6879
it('should enable exportWithFormatter as a grid option and expect empty string when associated item property is undefined', () => {
6980
mockColumn.exportWithFormatter = true;
70-
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
81+
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
7182
expect(output).toBe('');
7283
});
7384

7485
it('should expect empty string when associated item property is undefined and has no formatter defined', () => {
75-
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, {} as SlickGrid, {});
86+
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, gridStub as SlickGrid, {});
7687
expect(output).toBe('');
7788
});
7889
});

0 commit comments

Comments
 (0)