Skip to content

feat(igx-hierarchical-grid): export hierarchical grid #9206

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

Merged
merged 25 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ac06346
feat(igx-hierarchical-grid): export hierarchical grid
onlyexeption Mar 25, 2021
42b0ec5
feat(hierarchical-grid): excel export major refactoring
onlyexeption Mar 30, 2021
cbf04af
feat(hierarchical-grid): major refactoring
onlyexeption Apr 1, 2021
a2f69d1
feat(hierarchical-grid): major base-export refactoring
onlyexeption Apr 6, 2021
0858599
feat(hierarchical-grid): address PR comments
onlyexeption Apr 13, 2021
404743b
chore(*): add type to owner property
onlyexeption Apr 13, 2021
e2a1543
Merge branch 'master' into ibarakov/feat-5848-master
DiyanDimitrov Apr 15, 2021
7b3bba5
feat(hierarchical-grid): add tests for hGrid export + address PR comm…
onlyexeption Apr 15, 2021
68d7c09
Merge branch 'ibarakov/feat-5848-master' of https://github.com/Ignite…
onlyexeption Apr 15, 2021
cb4f0a0
Merge branch 'master' into ibarakov/feat-5848-master
gedinakova Apr 16, 2021
c72e9be
chore(*): minor changes regarding PR comments
onlyexeption Apr 16, 2021
41d6866
Merge branch 'ibarakov/feat-5848-master' of https://github.com/Ignite…
onlyexeption Apr 16, 2021
2e75f40
chore(*): fix expected data for styles.xml
onlyexeption Apr 16, 2021
8ada2ee
test(Excel): Fixed styles.xml template
gedinakova Apr 16, 2021
e903ac1
test(Excel): Fixed failing col visibility tests.
gedinakova Apr 16, 2021
4912115
Merge branch 'master' into ibarakov/feat-5848-master
gedinakova Apr 16, 2021
2edaef0
Merge branch 'ibarakov/feat-5848-master' of https://github.com/Ignite…
gedinakova Apr 16, 2021
519f9cf
Merge branch 'master' into ibarakov/feat-5848-master
gedinakova Apr 17, 2021
81e2043
Merge branch 'master' into ibarakov/feat-5848-master
gedinakova Apr 19, 2021
04e6b45
chore(*): fix tests + address PR comments
onlyexeption Apr 19, 2021
56a6dd5
Merge branch 'ibarakov/feat-5848-master' of https://github.com/Ignite…
onlyexeption Apr 19, 2021
c822ee2
chore(*): replace property
onlyexeption Apr 19, 2021
5467f57
chore(*): edit changelog.md
onlyexeption Apr 19, 2021
626dea1
chore(*): remove leftover comment
onlyexeption Apr 19, 2021
be9fd72
Merge branch 'master' into ibarakov/feat-5848-master
gedinakova Apr 19, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class CharSeparatedValueData {
return '';
}

this._isSpecialData = ExportUtilities.isSpecialData(this._data);
this._isSpecialData = ExportUtilities.isSpecialData(this._data[0]);
this._escapeCharacters.push(this._delimiter);

this._headerRecord = this.processHeaderRecord(keys);
Expand All @@ -52,7 +52,7 @@ export class CharSeparatedValueData {
done('');
}

this._isSpecialData = ExportUtilities.isSpecialData(this._data);
this._isSpecialData = ExportUtilities.isSpecialData(this._data[0]);
this._escapeCharacters.push(this._delimiter);

this._headerRecord = this.processHeaderRecord(keys);
Expand Down
27 changes: 23 additions & 4 deletions projects/igniteui-angular/src/lib/services/excel/excel-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ExcelElementsFactory } from './excel-elements-factory';
import { ExcelFolderTypes } from './excel-enums';
import { IgxExcelExporterOptions } from './excel-exporter-options';
import { IExcelFolder } from './excel-interfaces';
import { IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
import { ExportRecordType, IExportRecord, IgxBaseExporter, DEFAULT_OWNER } from '../exporter-common/base-export-service';
import { ExportUtilities } from '../exporter-common/export-utilities';
import { WorksheetData } from './worksheet-data';
import { IBaseEventArgs } from '../../core/utils';
Expand Down Expand Up @@ -72,9 +72,13 @@ export class IgxExcelExporterService extends IgxBaseExporter {
}

protected exportDataImplementation(data: IExportRecord[], options: IgxExcelExporterOptions): void {
const level = data[0]?.level;
const firstDataElement = data[0];
let rootKeys;
let columnCount;
let columnWidths;
let indexOfLastPinnedColumn;

if (typeof level !== 'undefined') {
if (typeof firstDataElement !== 'undefined') {
let maxLevel = 0;

data.forEach((r) => {
Expand All @@ -84,9 +88,24 @@ export class IgxExcelExporterService extends IgxBaseExporter {
if (maxLevel > 7) {
throw Error('Can create an outline of up to eight levels!');
}

if (firstDataElement.type === ExportRecordType.HierarchicalGridRecord) {
columnCount = data
.map(a => this._ownersMap.get(a.owner).columns.length + a.level)
.sort((a,b) => b - a)[0];

rootKeys = this._ownersMap.get(firstDataElement.owner).columns.map(c => c.field);
} else {
const defaultOwner = this._ownersMap.get(DEFAULT_OWNER);
columnWidths = defaultOwner.columnWidths;
indexOfLastPinnedColumn = defaultOwner.indexOfLastPinnedColumn;
columnCount = defaultOwner.columns.length;
rootKeys = defaultOwner.columns.map(c => c.field);
}
}

const worksheetData = new WorksheetData(data, this.columnWidthList, options, this._indexOfLastPinnedColumn, this._sort);
const worksheetData =
new WorksheetData(data, options, this._sort, columnCount, rootKeys, indexOfLastPinnedColumn, columnWidths);

this._xlsx = new (JSZip as any).default();

Expand Down
103 changes: 63 additions & 40 deletions projects/igniteui-angular/src/lib/services/excel/excel-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { WorksheetData } from './worksheet-data';

import * as JSZip from 'jszip';
import { yieldingLoop } from '../../core/utils';
import { ExportRecordType } from '../exporter-common/base-export-service';

/**
* @hidden
Expand Down Expand Up @@ -67,9 +68,10 @@ export class WorksheetFile implements IExcelFile {
return new Promise<void>(resolve => {
this.prepareDataAsync(worksheetData, (cols, rows) => {
const hasTable = !worksheetData.isEmpty && worksheetData.options.exportAsTable;
const isHierarchicalGrid = worksheetData.data[0]?.type === ExportRecordType.HierarchicalGridRecord;

folder.file('sheet1.xml', ExcelStrings.getSheetXML(
this.dimension, this.freezePane, cols, rows, hasTable, this.maxOutlineLevel));
this.dimension, this.freezePane, cols, rows, hasTable, this.maxOutlineLevel, isHierarchicalGrid));
resolve();
});
});
Expand All @@ -85,42 +87,53 @@ export class WorksheetFile implements IExcelFile {
this.dimension = 'A1';
done('', sheetData);
} else {
sheetData += '<sheetData>';
const isHierarchicalGrid = worksheetData.data[0].type === ExportRecordType.HierarchicalGridRecord;

const height = worksheetData.options.rowHeight;
this.rowHeight = height ? ' ht="' + height + '" customHeight="1"' : '';
sheetData += `<row r="1"${this.rowHeight}>`;
const rowStyle = isHierarchicalGrid ? ' s="3"' : '';
this.rowHeight = height ? ` ht="${height}" customHeight="1"` : '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably extract the setting rowHeight logic in a method (I saw this used elsewhere).


sheetData += `<sheetData><row r="1"${this.rowHeight}>`;

for (let i = 0; i < worksheetData.columnCount; i++) {
for (let i = 0; i < worksheetData.rootKeys.length; i++) {
const column = ExcelStrings.getExcelColumn(i) + 1;
const value = dictionary.saveValue(worksheetData.keys[i], i, true);
sheetData += `<c r="${column}" t="s"><v>${value}</v></c>`;
const value = dictionary.saveValue(worksheetData.rootKeys[i], true);
sheetData += `<c r="${column}"${rowStyle} t="s"><v>${value}</v></c>`;
}
sheetData += '</row>';

this.dimension = 'A1:' + ExcelStrings.getExcelColumn(worksheetData.columnCount - 1) + worksheetData.rowCount;
cols += '<cols>';

for (let i = 0; i < worksheetData.columnCount; i++) {
const width = dictionary.columnWidths[i];
// Use the width provided in the options if it exists
let widthInTwips = worksheetData.options.columnWidth !== undefined ?
worksheetData.options.columnWidth :
Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH);
if (!(widthInTwips > 0)) {
widthInTwips = WorksheetFile.MIN_WIDTH;
if (!isHierarchicalGrid) {
this.dimension = 'A1:' + ExcelStrings.getExcelColumn(worksheetData.columnCount - 1) + worksheetData.rowCount;
cols += '<cols>';

for (let i = 0; i < worksheetData.columnCount; i++) {
const width = dictionary.columnWidths[i];
// Use the width provided in the options if it exists
let widthInTwips = worksheetData.options.columnWidth !== undefined ?
worksheetData.options.columnWidth :
Math.max(((width / 96) * 14.4), WorksheetFile.MIN_WIDTH);
if (!(widthInTwips > 0)) {
widthInTwips = WorksheetFile.MIN_WIDTH;
}

cols += `<col min="${(i + 1)}" max="${(i + 1)}" width="${widthInTwips}" customWidth="1"/>`;
}

cols += `<col min="${(i + 1)}" max="${(i + 1)}" width="${widthInTwips}" customWidth="1"/>`;
}
cols += '</cols>';

cols += '</cols>';
const indexOfLastPinnedColumn = worksheetData.indexOfLastPinnedColumn;

if (worksheetData.indexOfLastPinnedColumn !== -1 &&
!worksheetData.options.ignorePinning &&
!worksheetData.options.ignoreColumnsOrder) {
const frozenColumnCount = worksheetData.indexOfLastPinnedColumn + 1;
const firstCell = ExcelStrings.getExcelColumn(frozenColumnCount) + '1';
this.freezePane = `<pane xSplit="${frozenColumnCount}" topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
if (indexOfLastPinnedColumn !== -1 &&
!worksheetData.options.ignorePinning &&
!worksheetData.options.ignoreColumnsOrder) {
const frozenColumnCount = indexOfLastPinnedColumn + 1;
const firstCell = ExcelStrings.getExcelColumn(frozenColumnCount) + '1';
this.freezePane =
`<pane xSplit="${frozenColumnCount}" topLeftCell="${firstCell}" activePane="topRight" state="frozen"/>`;
}
} else {
const columnWidth = worksheetData.options.columnWidth ? worksheetData.options.columnWidth : 20;
cols += `<cols><col min="1" max="${worksheetData.columnCount}" width="${columnWidth}" customWidth="1"/></cols>`;
}

this.processDataRecordsAsync(worksheetData, (rows) => {
Expand All @@ -146,41 +159,49 @@ export class WorksheetFile implements IExcelFile {
}

private processRow(worksheetData: WorksheetData, i: number) {
const rowData = new Array(worksheetData.columnCount + 2);
const record = worksheetData.data[i - 1];
const sHidden = record.hidden ? ` hidden="1"` : '';

const isHierarchicalGrid = record.type === ExportRecordType.HeaderRecord || record.type === ExportRecordType.HierarchicalGridRecord;
const rowData = new Array(worksheetData.columnCount + 2);

const rowLevel = record.level;
const outlineLevel = rowLevel > 0 ? ` outlineLevel="${rowLevel}"` : '';

this.maxOutlineLevel = this.maxOutlineLevel < rowLevel ? rowLevel : this.maxOutlineLevel;

const sHidden = record.hidden ? ` hidden="1"` : '';

rowData[0] = `<row r="${(i + 1)}"${this.rowHeight}${outlineLevel}${sHidden}>`;

for (let j = 0; j < worksheetData.columnCount; j++) {
const cellData = WorksheetFile.getCellData(worksheetData, i, j);
const keys = Object.keys(record.data);

for (let j = 0; j < keys.length; j++) {
const col = j + (isHierarchicalGrid ? rowLevel : 0);

const cellData = WorksheetFile.getCellData(worksheetData, i, col, keys[j]);

rowData[j + 1] = cellData;
}

rowData[worksheetData.columnCount + 1] = '</row>';
rowData[keys.length + 1] = '</row>';

return rowData.join('');
}

/* eslint-disable @typescript-eslint/member-ordering */
private static getCellData(worksheetData: WorksheetData, row: number, column: number): string {
private static getCellData(worksheetData: WorksheetData, row: number, column: number, key: string): string {
const dictionary = worksheetData.dataDictionary;
const columnName = ExcelStrings.getExcelColumn(column) + (row + 1);
const columnHeader = worksheetData.keys[column];
const fullRow = worksheetData.data[row - 1];
const isHeaderRecord = fullRow.type === ExportRecordType.HeaderRecord;

const cellValue = worksheetData.isSpecialData ?
fullRow.data :
fullRow.data[columnHeader];
fullRow.data[key];

if (cellValue === undefined || cellValue === null) {
return `<c r="${columnName}" s="1"/>`;
} else {
const savedValue = dictionary.saveValue(cellValue, column, false);
const savedValue = dictionary.saveValue(cellValue, isHeaderRecord);
const isSavedAsString = savedValue !== -1;

const isSavedAsDate = !isSavedAsString && cellValue instanceof Date;
Expand All @@ -195,7 +216,7 @@ export class WorksheetFile implements IExcelFile {

const type = isSavedAsString ? ` t="s"` : isSavedAsDate ? ` t="d"` : '';

const format = isSavedAsString ? '' : isSavedAsDate ? ` s="2"` : ` s="1"`;
const format = isHeaderRecord ? ` s="3"` : isSavedAsString ? '' : isSavedAsDate ? ` s="2"` : ` s="1"`;

return `<c r="${columnName}"${type}${format}><v>${value}</v></c>`;
}
Expand All @@ -210,7 +231,9 @@ export class StyleFile implements IExcelFile {
public writeElement(folder: JSZip, worksheetData: WorksheetData) {
const hasNumberValues = worksheetData.dataDictionary && worksheetData.dataDictionary.hasNumberValues;
const hasDateValues = worksheetData.dataDictionary && worksheetData.dataDictionary.hasDateValues;
folder.file('styles.xml', ExcelStrings.getStyles(hasNumberValues, hasDateValues));
const isHierarchicalGrid = worksheetData.data[0]?.type === ExportRecordType.HierarchicalGridRecord;

folder.file('styles.xml', ExcelStrings.getStyles(hasNumberValues, hasDateValues, isHierarchicalGrid));
}
}

Expand Down Expand Up @@ -261,7 +284,7 @@ export class TablesFile implements IExcelFile {
const columnCount = worksheetData.columnCount;
const lastColumn = ExcelStrings.getExcelColumn(columnCount - 1) + worksheetData.rowCount;
const dimension = 'A1:' + lastColumn;
const values = worksheetData.keys;
const values = worksheetData.rootKeys;
let sortString = '';

let tableColumns = '<tableColumns count="' + columnCount + '">';
Expand Down
Loading