Skip to content

Commit 11c93bc

Browse files
authored
Merge branch 'master' into ttonev-fix-10806
2 parents 5a1d428 + 97fcd04 commit 11c93bc

File tree

10 files changed

+168
-49
lines changed

10 files changed

+168
-49
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ All notable changes for each version of this project will be documented in this
1313
```
1414
- `IgxTabs` have full right-to-left (RTL) support.
1515

16+
- `IgxExcelExporterService`
17+
- Added support for exporting the grids' headers by default when the data is empty. This behavior can be controlled by the `alwaysExportHeaders` option of the IgxExcelExporterOptions object.
18+
1619
### General
1720

1821
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`

projects/igniteui-angular/src/lib/services/excel/excel-exporter-grid.spec.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ describe('Excel Exporter', () => {
389389

390390
it('should not export columns when \'columnExporting\' is canceled.', async () => {
391391
const fix = TestBed.createComponent(GridIDNameJobTitleComponent);
392+
options.alwaysExportHeaders = false;
393+
392394
fix.detectChanges();
393395
await wait();
394396

@@ -477,6 +479,8 @@ describe('Excel Exporter', () => {
477479

478480
it('should not export rows when \'rowExporting\' is canceled.', async () => {
479481
const fix = TestBed.createComponent(GridIDNameJobTitleComponent);
482+
options.alwaysExportHeaders = false;
483+
480484
fix.detectChanges();
481485
await wait();
482486

@@ -902,12 +906,13 @@ describe('Excel Exporter', () => {
902906
await exportAndVerify(hGrid, options, actualData.exportMultiColumnHeadersDataWithSkippedParentMCH);
903907
});
904908

905-
it('should export empty file when all parent multi column headers are skipped', async () => {
909+
it('should export empty file when all parent multi column headers are skipped and alwaysExportHeaders is false', async () => {
906910
const fix = TestBed.createComponent(IgxHierarchicalGridMultiColumnHeadersExportComponent);
907911
fix.detectChanges();
908912

909913
const hGrid = fix.componentInstance.hGrid;
910914
options = createExportOptions('HierarchicalGridMCHExcelExport');
915+
options.alwaysExportHeaders = false;
911916

912917
exporter.columnExporting.subscribe((args: IColumnExportingEventArgs) => {
913918
if (args.header === 'General Information' || args.header === 'Address Information' || args.field === 'CustomerID') {
@@ -918,6 +923,19 @@ describe('Excel Exporter', () => {
918923

919924
await exportAndVerify(hGrid, options, actualData.exportMultiColumnHeadersDataWithAllParentsSkipped);
920925
});
926+
927+
it('should export headers when exporting empty hierarchical grid with multi column headers', async () => {
928+
const fix = TestBed.createComponent(IgxHierarchicalGridMultiColumnHeadersExportComponent);
929+
fix.detectChanges();
930+
931+
const hGrid = fix.componentInstance.hGrid;
932+
fix.componentInstance.data = [];
933+
options = createExportOptions('HierarchicalGridMCHExcelExport');
934+
935+
fix.detectChanges();
936+
937+
await exportAndVerify(hGrid, options, actualData.exportEmptyMultiColumnHeadersDataWithExportedHeaders);
938+
});
921939
});
922940

923941
describe('', () => {
@@ -1094,6 +1112,13 @@ describe('Excel Exporter', () => {
10941112

10951113
await exportAndVerify(treeGrid, options, actualData.treeGridWithAdvancedFilters);
10961114
});
1115+
1116+
it('should export headers when exporting empty tree grid.', async () => {
1117+
fix.componentInstance.data = [];
1118+
fix.detectChanges();
1119+
1120+
await exportAndVerify(treeGrid, options, actualData.emptyTreeGridWithExportedHeaders);
1121+
});
10971122
});
10981123

10991124
describe('', () => {
@@ -1160,6 +1185,14 @@ describe('Excel Exporter', () => {
11601185
fix.detectChanges();
11611186
await exportAndVerify(grid, options, actualData.exportFrozenMultiColumnHeadersData, false);
11621187
});
1188+
1189+
it('should export headers when exporting empty grid with multi column headers', async () => {
1190+
fix.componentInstance.data = [];
1191+
1192+
fix.detectChanges();
1193+
1194+
await exportAndVerify(grid, options, actualData.exportEmptyGridWithMultiColumnHeadersData, false);
1195+
});
11631196
});
11641197

11651198
const getExportedData = (grid, exportOptions: IgxExcelExporterOptions) => {

projects/igniteui-angular/src/lib/services/excel/excel-exporter.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ describe('Excel Exporter', () => {
2525

2626
/* ExportData() tests */
2727
it('should not fail when data is empty.', async () => {
28+
options.alwaysExportHeaders = false;
29+
2830
const wrapper = await getExportedData([], options);
2931

3032
wrapper.verifyStructure();
3133
await wrapper.verifyTemplateFilesContent();
3234
});
3335

3436
it('should export empty objects successfully.', async () => {
37+
options.alwaysExportHeaders = false;
3538
const wrapper = await getExportedData(SampleTestData.emptyObjectData(), options);
3639

3740
wrapper.verifyStructure();

projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ export class IgxExcelExporterService extends IgxBaseExporter {
123123
columnCount = columns.length;
124124
rootKeys = columns.map(c => c.field);
125125
}
126+
} else {
127+
const ownersKeys = Array.from(this._ownersMap.keys());
128+
129+
defaultOwner = this._ownersMap.get(ownersKeys[0]);
130+
columnWidths = defaultOwner.columnWidths;
131+
columnCount = defaultOwner.columns.filter(col => !col.skip && col.headerType === HeaderType.ColumnHeader).length;
126132
}
127133

128134
const worksheetData =

projects/igniteui-angular/src/lib/services/excel/excel-files.ts

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class CoreFile implements IExcelFile {
3838
*/
3939
export class WorkbookRelsFile implements IExcelFile {
4040
public writeElement(folder: JSZip, worksheetData: WorksheetData) {
41-
const hasSharedStrings = worksheetData.isEmpty === false;
41+
const hasSharedStrings = !worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders;
4242
folder.file('workbook.xml.rels', ExcelStrings.getWorkbookRels(hasSharedStrings));
4343
}
4444
}
@@ -71,11 +71,11 @@ export class WorksheetFile implements IExcelFile {
7171
public async writeElementAsync(folder: JSZip, worksheetData: WorksheetData) {
7272
return new Promise<void>(resolve => {
7373
this.prepareDataAsync(worksheetData, (cols, rows) => {
74-
const hasTable = !worksheetData.isEmpty && worksheetData.options.exportAsTable;
75-
const isHierarchicalGrid = worksheetData.data[0]?.type === ExportRecordType.HierarchicalGridRecord;
74+
const hasTable = (!worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders)
75+
&& worksheetData.options.exportAsTable;
7676

7777
folder.file('sheet1.xml', ExcelStrings.getSheetXML(
78-
this.dimension, this.freezePane, cols, rows, hasTable, this.maxOutlineLevel, isHierarchicalGrid));
78+
this.dimension, this.freezePane, cols, rows, hasTable, this.maxOutlineLevel, worksheetData.isHierarchical));
7979
resolve();
8080
});
8181
});
@@ -87,14 +87,15 @@ export class WorksheetFile implements IExcelFile {
8787
const dictionary = worksheetData.dataDictionary;
8888
this.rowIndex = 0;
8989

90-
if (worksheetData.isEmpty) {
90+
if (worksheetData.isEmpty && (!worksheetData.options.alwaysExportHeaders || worksheetData.owner.columns.length === 0)) {
9191
sheetData += '<sheetData/>';
9292
this.dimension = 'A1';
9393
done('', sheetData);
9494
} else {
9595
const owner = worksheetData.owner;
96-
const isHierarchicalGrid = worksheetData.data[0].type === ExportRecordType.HierarchicalGridRecord;
96+
const isHierarchicalGrid = worksheetData.isHierarchical;
9797
const hasMultiColumnHeader = worksheetData.hasMultiColumnHeader;
98+
9899
const hasUserSetIndex = owner.columns.some(col => col.exportIndex !== undefined);
99100

100101
const height = worksheetData.options.rowHeight;
@@ -182,7 +183,7 @@ export class WorksheetFile implements IExcelFile {
182183
const indexOfLastPinnedColumn = worksheetData.indexOfLastPinnedColumn;
183184
const frozenColumnCount = indexOfLastPinnedColumn + 1;
184185
let firstCell = ExcelStrings.getExcelColumn(frozenColumnCount) + freezeHeaders;
185-
if (indexOfLastPinnedColumn !== -1 &&
186+
if (indexOfLastPinnedColumn !== undefined && indexOfLastPinnedColumn !== -1 &&
186187
!worksheetData.options.ignorePinning &&
187188
!worksheetData.options.ignoreColumnsOrder) {
188189
this.freezePane =
@@ -209,7 +210,7 @@ export class WorksheetFile implements IExcelFile {
209210
sheetData += rows;
210211
sheetData += '</sheetData>';
211212

212-
if (hasMultiColumnHeader) {
213+
if (hasMultiColumnHeader && this.mergeCellsCounter > 0) {
213214
sheetData += `<mergeCells count="${this.mergeCellsCounter}">${this.mergeCellStr}</mergeCells>`;
214215
}
215216

@@ -223,40 +224,41 @@ export class WorksheetFile implements IExcelFile {
223224
const height = worksheetData.options.rowHeight;
224225
this.rowHeight = height ? ' ht="' + height + '" customHeight="1"' : '';
225226

226-
const isHierarchicalGrid =
227-
worksheetData.data.some(r => r.type === ExportRecordType.HeaderRecord || r.type === ExportRecordType.HierarchicalGridRecord);
227+
const isHierarchicalGrid = worksheetData.isHierarchical;
228228
const hasUserSetIndex = worksheetData.owner.columns.some(c => c.exportIndex !== undefined);
229229

230230
let recordHeaders = [];
231231

232232
yieldingLoop(worksheetData.rowCount - 1, 1000,
233233
(i) => {
234-
if (!isHierarchicalGrid) {
235-
if (hasUserSetIndex) {
236-
recordHeaders = worksheetData.rootKeys;
234+
if (!worksheetData.isEmpty){
235+
if (!isHierarchicalGrid) {
236+
if (hasUserSetIndex) {
237+
recordHeaders = worksheetData.rootKeys;
238+
} else {
239+
recordHeaders = worksheetData.owner.columns
240+
.filter(c => c.headerType !== HeaderType.MultiColumnHeader && !c.skip)
241+
.sort((a, b) => a.startIndex-b.startIndex)
242+
.sort((a, b) => a.pinnedIndex-b.pinnedIndex)
243+
.map(c => c.field);
244+
}
237245
} else {
238-
recordHeaders = worksheetData.owner.columns
239-
.filter(c => c.headerType !== HeaderType.MultiColumnHeader && !c.skip)
240-
.sort((a, b) => a.startIndex-b.startIndex)
241-
.sort((a, b) => a.pinnedIndex-b.pinnedIndex)
242-
.map(c => c.field);
243-
}
244-
} else {
245-
const record = worksheetData.data[i];
246+
const record = worksheetData.data[i];
246247

247-
if (record.type === ExportRecordType.HeaderRecord) {
248-
const recordOwner = worksheetData.owners.get(record.owner);
249-
const hasMultiColumnHeaders = recordOwner.columns.some(c => !c.skip && c.headerType === HeaderType.MultiColumnHeader);
248+
if (record.type === ExportRecordType.HeaderRecord) {
249+
const recordOwner = worksheetData.owners.get(record.owner);
250+
const hasMultiColumnHeaders = recordOwner.columns.some(c => !c.skip && c.headerType === HeaderType.MultiColumnHeader);
250251

251-
if (hasMultiColumnHeaders) {
252-
this.hGridPrintMultiColHeaders(worksheetData, rowDataArr, record, recordOwner);
252+
if (hasMultiColumnHeaders) {
253+
this.hGridPrintMultiColHeaders(worksheetData, rowDataArr, record, recordOwner);
254+
}
253255
}
256+
257+
recordHeaders = Object.keys(worksheetData.data[i].data);
254258
}
255259

256-
recordHeaders = Object.keys(worksheetData.data[i].data);
260+
rowDataArr.push(this.processRow(worksheetData, i, recordHeaders, isHierarchicalGrid));
257261
}
258-
259-
rowDataArr.push(this.processRow(worksheetData, i, recordHeaders, isHierarchicalGrid));
260262
},
261263
() => {
262264
done(rowDataArr.join(''));
@@ -404,7 +406,8 @@ export class WorkbookFile implements IExcelFile {
404406
*/
405407
export class ContentTypesFile implements IExcelFile {
406408
public writeElement(folder: JSZip, worksheetData: WorksheetData) {
407-
folder.file('[Content_Types].xml', ExcelStrings.getContentTypesXML(!worksheetData.isEmpty, worksheetData.options.exportAsTable));
409+
const hasSharedStrings = !worksheetData.isEmpty || worksheetData.options.alwaysExportHeaders;
410+
folder.file('[Content_Types].xml', ExcelStrings.getContentTypesXML(hasSharedStrings, worksheetData.options.exportAsTable));
408411
}
409412
}
410413

@@ -436,7 +439,10 @@ export class TablesFile implements IExcelFile {
436439
public writeElement(folder: JSZip, worksheetData: WorksheetData) {
437440
const columnCount = worksheetData.columnCount;
438441
const lastColumn = ExcelStrings.getExcelColumn(columnCount - 1) + worksheetData.rowCount;
439-
const dimension = 'A1:' + lastColumn;
442+
const autoFilterDimension = 'A1:' + lastColumn;
443+
const tableDimension = worksheetData.isEmpty
444+
? 'A1:' + ExcelStrings.getExcelColumn(columnCount - 1) + (worksheetData.rowCount + 1)
445+
: autoFilterDimension;
440446
const hasUserSetIndex = worksheetData.owner.columns.some(c => c.exportIndex !== undefined);
441447
const values = hasUserSetIndex
442448
? worksheetData.rootKeys
@@ -463,7 +469,7 @@ export class TablesFile implements IExcelFile {
463469
sortString = `<sortState ref="A2:${lastColumn}"><sortCondition descending="${dir}" ref="${sc}1:${sc}15"/></sortState>`;
464470
}
465471

466-
folder.file('table1.xml', ExcelStrings.getTablesXML(dimension, tableColumns, sortString));
472+
folder.file('table1.xml', ExcelStrings.getTablesXML(autoFilterDimension, tableDimension, tableColumns, sortString));
467473
}
468474
}
469475

projects/igniteui-angular/src/lib/services/excel/excel-folders.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class XLExcelFolder implements IExcelFolder {
7070
ExcelFileTypes.WorkbookFile
7171
];
7272

73-
if (!data.isEmpty) {
73+
if (!data.isEmpty || data.options.alwaysExportHeaders) {
7474
retVal.push(ExcelFileTypes.SharedStringsFile);
7575
}
7676

@@ -84,7 +84,7 @@ export class XLExcelFolder implements IExcelFolder {
8484
ExcelFolderTypes.WorksheetsExcelFolder
8585
];
8686

87-
if (!data.isEmpty && data.options.exportAsTable) {
87+
if ((!data.isEmpty || data.options.alwaysExportHeaders) && data.options.exportAsTable) {
8888
retVal.push(ExcelFolderTypes.TablesExcelFolder);
8989
}
9090

@@ -133,7 +133,7 @@ export class WorksheetsExcelFolder implements IExcelFolder {
133133
}
134134

135135
public childFolders(data: WorksheetData) {
136-
return data.isEmpty || !data.options.exportAsTable ? [] : [ExcelFolderTypes.WorksheetsRelsExcelFolder];
136+
return (data.isEmpty && !data.options.alwaysExportHeaders) || !data.options.exportAsTable ? [] : [ExcelFolderTypes.WorksheetsRelsExcelFolder];
137137
}
138138
}
139139

projects/igniteui-angular/src/lib/services/excel/excel-strings.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ export class ExcelStrings {
4545
return retVal;
4646
}
4747

48-
public static getSheetXML(dimension: string, freezePane: string, cols: string, sheetData: string, hasTable: boolean, outlineLevel = 0, isHierarchicalGrid: boolean): string {
48+
public static getSheetXML(dimension: string, freezePane: string, cols: string, sheetData: string, hasTable: boolean, outlineLevel = 0, isHierarchical: boolean): string {
4949
const hasOutline = outlineLevel > 0;
5050
const tableParts = hasTable ? '<tableParts count="1"><tablePart r:id="rId1"/></tableParts>' : '';
5151
const sheetOutlineProp = hasOutline ? '<sheetPr><outlinePr summaryBelow="0"/></sheetPr>' : '';
5252
const sOutlineLevel = hasOutline ? `outlineLevelRow="${outlineLevel}"` : '';
53-
const dimensions = isHierarchicalGrid ? '' : `<dimension ref="${dimension}"/>`;
53+
const dimensions = isHierarchical ? '' : `<dimension ref="${dimension}"/>`;
5454

5555
// return ExcelStrings.XML_STRING +
5656
// '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"><dimension ref="' + dimension + '"/><sheetViews><sheetView tabSelected="1" workbookViewId="0">' + freezePane + '</sheetView></sheetViews><sheetFormatPr defaultRowHeight="15" x14ac:dyDescent="0.25"/>' + cols + sheetData + '<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>' + tableParts + '</worksheet>';
@@ -94,9 +94,9 @@ ${tableParts}</worksheet>`;
9494
return contentTypes;
9595
}
9696

97-
public static getTablesXML(dimension: string, tableColumns: string, sort: string): string {
98-
return `${ExcelStrings.XML_STRING}<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="Table1" displayName="Table1" ref="${dimension}" totalsRowShown="0">
99-
<autoFilter ref="${dimension}"/>${sort}${tableColumns}<tableStyleInfo name="TableStyleMedium2" showFirstColumn="0" showLastColumn="0" showRowStripes="1" showColumnStripes="0"/>
97+
public static getTablesXML(autoFilterDimension: string, tableDimension: string, tableColumns: string, sort: string): string {
98+
return `${ExcelStrings.XML_STRING}<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="Table1" displayName="Table1" ref="${tableDimension}" totalsRowShown="0">
99+
<autoFilter ref="${autoFilterDimension}"/>${sort}${tableColumns}<tableStyleInfo name="TableStyleMedium2" showFirstColumn="0" showLastColumn="0" showRowStripes="1" showColumnStripes="0"/>
100100
</table>`;
101101
}
102102
/* eslint-enable max-len */

0 commit comments

Comments
 (0)