Skip to content

Commit 8c4b435

Browse files
authored
Merge branch 'master' into sstoychev/reemit-events-master
2 parents 25029b5 + ed17d4e commit 8c4b435

File tree

13 files changed

+216
-56
lines changed

13 files changed

+216
-56
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/directives/toggle/toggle.directive.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export class IgxToggleDirective implements IToggleView, OnInit, OnDestroy {
241241
* this.myToggle.close();
242242
* ```
243243
*/
244-
public close() {
244+
public close(event?: Event) {
245245
// if toggle is collapsed do nothing
246246
// if there is close animation do nothing, toggle will close anyway
247247
const info = this.overlayService.getOverlayById(this._overlayId);
@@ -250,7 +250,7 @@ export class IgxToggleDirective implements IToggleView, OnInit, OnDestroy {
250250
return;
251251
}
252252

253-
this.overlayService.hide(this._overlayId);
253+
this.overlayService.hide(this._overlayId, event);
254254
}
255255

256256
/**

projects/igniteui-angular/src/lib/drop-down/drop-down.component.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IgxDropDownComponent, IgxDropDownModule } from './public_api';
88
import { ISelectionEventArgs } from './drop-down.common';
99
import { IgxTabsComponent, IgxTabsModule } from '../tabs/tabs/public_api';
1010
import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';
11-
import { CancelableEventArgs } from '../core/utils';
11+
import { CancelableEventArgs, IBaseCancelableBrowserEventArgs } from '../core/utils';
1212
import { configureTestSuite } from '../test-utils/configure-suite';
1313
import { take } from 'rxjs/operators';
1414
import { IgxDropDownGroupComponent } from './drop-down-group.component';
@@ -524,6 +524,47 @@ describe('IgxDropDown ', () => {
524524
};
525525
expect(dropdown.selectionChanging.emit).toHaveBeenCalledWith(canceledSelectionArgs);
526526
}));
527+
it('should provide correct event argument when closing through keyboard', fakeAsync(() => {
528+
spyOn(dropdown.closing, 'emit').and.callThrough();
529+
const dropdownElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROP_DOWN_BASE}`));
530+
531+
dropdown.toggle();
532+
tick();
533+
fixture.detectChanges();
534+
let focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`));
535+
expect(focusedItem).toBeDefined();
536+
537+
let eventArgs: IBaseCancelableBrowserEventArgs;
538+
dropdown.closing.pipe(take(1)).subscribe((args: IBaseCancelableBrowserEventArgs) => {
539+
eventArgs = args;
540+
});
541+
542+
UIInteractions.triggerEventHandlerKeyDown('escape', dropdownElement);
543+
tick();
544+
fixture.detectChanges();
545+
expect(dropdown.closing.emit).toHaveBeenCalledTimes(1);
546+
expect(eventArgs.event).toBeDefined();
547+
expect((eventArgs.event as KeyboardEvent).type).toEqual('keydown');
548+
expect((eventArgs.event as KeyboardEvent).key).toEqual('escape');
549+
550+
dropdown.toggle();
551+
tick();
552+
fixture.detectChanges();
553+
focusedItem = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOCUSED}`));
554+
expect(focusedItem).toBeDefined();
555+
556+
dropdown.closing.pipe(take(1)).subscribe((args: IBaseCancelableBrowserEventArgs) => {
557+
eventArgs = args;
558+
});
559+
560+
UIInteractions.triggerEventHandlerKeyDown('enter', dropdownElement);
561+
tick();
562+
fixture.detectChanges();
563+
expect(dropdown.closing.emit).toHaveBeenCalledTimes(2);
564+
expect(eventArgs.event).toBeDefined();
565+
expect((eventArgs.event as KeyboardEvent).type).toEqual('keydown');
566+
expect((eventArgs.event as KeyboardEvent).key).toEqual('enter');
567+
}));
527568
it('should be able to change selection when manipulating ISelectionEventArgs', fakeAsync(() => {
528569
expect(dropdown.selectedItem).toEqual(null);
529570
dropdown.toggle();

projects/igniteui-angular/src/lib/drop-down/drop-down.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
242242
* this.dropdown.close();
243243
* ```
244244
*/
245-
public close() {
246-
this.toggleDirective.close();
245+
public close(event?: Event) {
246+
this.toggleDirective.close(event);
247247
}
248248

249249
/**
@@ -438,7 +438,7 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
438438
/** Keydown Handler */
439439
public onItemActionKey(key: DropDownActionKey, event?: Event) {
440440
super.onItemActionKey(key, event);
441-
this.close();
441+
this.close(event);
442442
}
443443

444444
/**
@@ -527,7 +527,7 @@ export class IgxDropDownComponent extends IgxDropDownBaseDirective implements ID
527527
}
528528
}
529529
if (event) {
530-
this.toggleDirective.close();
530+
this.toggleDirective.close(event);
531531
}
532532
} else {
533533
throw new Error('Please provide a valid drop-down item for the selection!');

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

0 commit comments

Comments
 (0)