diff --git a/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts b/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts index a05aa9219ab..8b4ec7f5fcd 100644 --- a/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts @@ -1210,6 +1210,10 @@ export interface IForOfState { chunkSize?: number; } +export interface IForOfDataChangingEventArgs { + containerSize: number; +} + @Directive({ selector: '[igxGridFor][igxGridForOf]' }) @@ -1235,6 +1239,13 @@ export class IgxGridForOfDirective extends IgxForOfDirective implements On return this.igxForOf; } + /** + * @hidden @internal + * An event that is emitted after data has been changed but before the view is refreshed + */ + @Output() + public onDataChanging = new EventEmitter(); + ngOnInit() { this.syncService.setMaster(this); super.ngOnInit(); @@ -1411,6 +1422,10 @@ export class IgxGridForOfDirective extends IgxForOfDirective implements On if (this._differ) { const changes = this._differ.diff(this.igxForOf); if (changes) { + const args: IForOfDataChangingEventArgs = { + containerSize: this.igxForContainerSize + }; + this.onDataChanging.emit(args); // re-init cache. if (!this.igxForOf) { return; @@ -1423,6 +1438,7 @@ export class IgxGridForOfDirective extends IgxForOfDirective implements On this.syncService.resetMaster(); } this.syncService.setMaster(this); + this.igxForContainerSize = args.containerSize; this._updateSizeCache(changes); this._applyChanges(); this._updateScrollOffset(); diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts index d85484400f5..8ad9238690b 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -585,6 +585,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements public set height(value: string) { if (this._height !== value) { this._height = value; + this._autoSize = false; requestAnimationFrame(() => { if (!this._destroyed) { this.reflow(); @@ -2431,6 +2432,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements private _pinnedColumnsText = ''; private _height = '100%'; private _width = '100%'; + protected _autoSize = false; private _rowHeight; protected _ngAfterViewInitPassed = false; private _horizontalForOfs; @@ -2447,7 +2449,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements private _columnWidth: string; private _columnWidthSetByUser = false; - private _defaultTargetRecordNumber = 10; + protected _defaultTargetRecordNumber = 10; private _summaryPosition = GridSummaryPosition.bottom; private _summaryCalculationMode = GridSummaryCalculationMode.rootAndChildLevels; @@ -2630,7 +2632,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.columnListDiffer.diff(this.columnList); this.markForCheck(); this.resetCaches(); - this._derivePossibleHeight(); this.columnList.changes .pipe(takeUntil(this.destroy$)) @@ -2758,6 +2759,10 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements vertScrDC.addEventListener('scroll', (evt) => { this.scrollHandler(evt); }); vertScrDC.addEventListener('wheel', () => { this.wheelHandler(); }); + this.verticalScrollContainer.onDataChanging.pipe(takeUntil(this.destroy$)).subscribe(($event) => { + this.calculateGridHeight(); + $event.containerSize = this.calcHeight; + }); this.verticalScrollContainer.onDataChanged.pipe(takeUntil(this.destroy$)).subscribe(() => { requestAnimationFrame(() => { if (!this._destroyed) { @@ -3863,29 +3868,12 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } /** - * @hidden + * @hidden @internal */ - protected get isPercentHeight() { + public get isPercentHeight() { return this._height && this._height.indexOf('%') !== -1; } - /** - * @hidden - * Sets this._height - */ - protected _derivePossibleHeight() { - if (!this.isPercentHeight || !this._height || !this.isAttachedToDom || this.rowBasedHeight === 0) { - return; - } - if (!this.nativeElement.parentNode || !this.nativeElement.parentNode.clientHeight) { - const viewPortHeight = document.documentElement.clientHeight; - this._height = this.rowBasedHeight <= viewPortHeight ? null : viewPortHeight.toString(); - } else { - const parentHeight = this.nativeElement.parentNode.getBoundingClientRect().height; - this._height = this.rowBasedHeight <= parentHeight ? null : this._height; - } - } - /** * @hidden * Sets columns defaultWidth property @@ -3922,21 +3910,12 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * Sets TBODY height i.e. this.calcHeight */ protected calculateGridHeight() { - this._derivePossibleHeight(); // TODO: Calculate based on grid density if (this.maxLevelHeaderDepth) { this.theadRow.nativeElement.style.height = `${(this.maxLevelHeaderDepth + 1) * this.defaultRowHeight + (this.allowFiltering && this.filterMode === FilterMode.quickFilter ? FILTER_ROW_HEIGHT : 0) + 1}px`; } this.summariesHeight = 0; - if (!this._height) { - this.calcHeight = null; - if (this.hasSummarizedColumns && this.rootSummariesEnabled) { - this.summariesHeight = this.summaryService.calcMaxSummaryHeight(); - } - return; - } - if (this.hasSummarizedColumns && this.rootSummariesEnabled) { this.summariesHeight = this.summaryService.calcMaxSummaryHeight(); } @@ -3977,30 +3956,38 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements /** * @hidden */ - protected _calculateGridBodyHeight() { + protected _calculateGridBodyHeight(): number { + if (!this._height) { + return null; + } const footerBordersAndScrollbars = this.tfoot.nativeElement.offsetHeight - this.tfoot.nativeElement.clientHeight; + let gridHeight; const computed = this.document.defaultView.getComputedStyle(this.nativeElement); const toolbarHeight = this.getToolbarHeight(); const pagingHeight = this.getPagingHeight(); const groupAreaHeight = this.getGroupAreaHeight(); - let gridHeight; + const renderedHeight = toolbarHeight + this.theadRow.nativeElement.offsetHeight + + this.summariesHeight + pagingHeight + groupAreaHeight + footerBordersAndScrollbars + + this.scr.nativeElement.clientHeight; if (this.isPercentHeight) { /*height in %*/ - if (computed.getPropertyValue('height').indexOf('%') === -1) { - gridHeight = parseInt(computed.getPropertyValue('height'), 10); - } else { - return this.defaultTargetBodyHeight; + if (!this.nativeElement.parentElement || + this.nativeElement.parentElement.clientHeight === renderedHeight) { + /* parent element is sized by the rendered elements which means + the grid should attempt a content-box style rendering */ + this._autoSize = true; + } + if (this._autoSize || computed.getPropertyValue('height').indexOf('%') !== -1) { + const bodyHeight = this.getDataBasedBodyHeight(); + return bodyHeight > 0 ? bodyHeight : null; } + gridHeight = parseInt(computed.getPropertyValue('height'), 10); } else { gridHeight = parseInt(this._height, 10); } - const height = Math.abs(gridHeight - toolbarHeight - - this.theadRow.nativeElement.offsetHeight - - this.summariesHeight - pagingHeight - - groupAreaHeight - footerBordersAndScrollbars - - this.scr.nativeElement.clientHeight); + const height = Math.abs(gridHeight - renderedHeight); if (height === 0 || isNaN(gridHeight)) { const bodyHeight = this.defaultTargetBodyHeight; @@ -4108,6 +4095,14 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements isScrollable); } + /** + * @hidden @internal + */ + protected getDataBasedBodyHeight(): number { + return !this.data || (this.data.length < this._defaultTargetRecordNumber) ? + 0 : this.defaultTargetBodyHeight; + } + /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts index 66d22d7e6de..819b2aaf404 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts @@ -118,7 +118,7 @@ describe('IgxGrid Component Tests', () => { }); }); - it('should initialize grid with remove virtualization', async () => { + it('should initialize grid with remote virtualization', async () => { const fix = TestBed.createComponent(IgxGridRemoteVirtualizationComponent); fix.detectChanges(); let rows = fix.componentInstance.instance.rowList.toArray(); @@ -147,6 +147,7 @@ describe('IgxGrid Component Tests', () => { it('height/width should be calculated depending on number of records', fakeAsync(() => { const fix = TestBed.createComponent(IgxGridTestComponent); + fix.componentInstance.grid.height = null; fix.detectChanges(); const grid = fix.componentInstance.grid; @@ -887,6 +888,7 @@ describe('IgxGrid Component Tests', () => { it('should match width and height of parent container when width/height are set in %', fakeAsync(() => { const fix = TestBed.createComponent(IgxGridWrappedInContComponent); const grid = fix.componentInstance.grid; + fix.componentInstance.data = fix.componentInstance.fullData; fix.componentInstance.outerWidth = 800; fix.componentInstance.outerHeight = 600; fix.componentInstance.grid.width = '50%'; @@ -901,6 +903,7 @@ describe('IgxGrid Component Tests', () => { it('should render 10 records if height is unset and parent container\'s height is unset', () => { const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + fix.componentInstance.data = fix.componentInstance.fullData; fix.detectChanges(); const defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; expect(defaultHeight).not.toBeNull(); @@ -909,8 +912,9 @@ describe('IgxGrid Component Tests', () => { expect(fix.componentInstance.grid.rowList.length).toBeGreaterThanOrEqual(10); }); - it('should render 10 records if height is 100% and parent container\'s height is unset', fakeAsync(() => { + it('should render pixel height when one is set and parent container\'s height is unset', fakeAsync(() => { const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + fix.componentInstance.data = fix.componentInstance.fullData; fix.componentInstance.grid.height = '700px'; tick(); fix.detectChanges(); @@ -925,12 +929,13 @@ describe('IgxGrid Component Tests', () => { there are fewer than 10 records in the data view`, fakeAsync(() => { const fix = TestBed.createComponent(IgxGridWrappedInContComponent); fix.componentInstance.grid.height = '100%'; - fix.componentInstance.data = fix.componentInstance.data.slice(0, 5); + fix.componentInstance.data = fix.componentInstance.semiData; tick(); fix.detectChanges(); const defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; - expect(defaultHeight).not.toBeNull(); - expect(parseInt(defaultHeight, 10)).toBeGreaterThan(200); + expect(defaultHeight).toBeNull(); + expect(fix.debugElement.query(By.css(TBODY_CLASS)) + .nativeElement.getBoundingClientRect().height).toBeGreaterThan(200); expect(fix.componentInstance.isVerticalScrollbarVisible()).toBeFalsy(); expect(fix.componentInstance.grid.rowList.length).toEqual(5); })); @@ -939,7 +944,7 @@ describe('IgxGrid Component Tests', () => { display density is changed`, fakeAsync(() => { const fix = TestBed.createComponent(IgxGridWrappedInContComponent); fix.componentInstance.grid.height = '100%'; - fix.componentInstance.data = fix.componentInstance.data.slice(0, 11); + fix.componentInstance.data = fix.componentInstance.fullData.slice(0, 11); fix.componentInstance.density = DisplayDensity.compact; tick(); fix.detectChanges(); @@ -951,6 +956,195 @@ describe('IgxGrid Component Tests', () => { expect(fix.componentInstance.grid.rowList.length).toEqual(11); })); + it('should keep auto-sizing if initial data is empty then set to a new array', fakeAsync(() => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + tick(); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); // initially body height is null in auto-sizing scenarios with empty data + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.data = fix.componentInstance.fullData; + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + const defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + })); + + it('should keep auto-sizing if initial data is set to empty array that is then filled', fakeAsync(() => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + tick(); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); // initially body height is null in auto-sizing scenarios with empty data + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.fullData.forEach(record => { + fix.componentInstance.data.push(record); + }); + fix.componentInstance.grid.cdr.markForCheck(); + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + const defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + })); + + it(`should not render with calcHeight null at any point when loading data and + auto-sizing is required and initial data is empty`, async () => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); // initially body height is null in auto-sizing scenarios with empty data + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.data = Array.from({ length: 100000 }, (_, i) => ({ 'ID': i, 'CompanyName': 'CN' + i })); + fix.detectChanges(); + await wait(500); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + const defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + }); + + it('should keep auto-sizing if initial data is set to small array that is then filled', fakeAsync(() => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + fix.componentInstance.data = fix.componentInstance.semiData; + tick(); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.data = fix.componentInstance.fullData; + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + const defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + })); + + it(`should render with calcHeight null if initial data is small but then + auto-size when it is filled`, async () => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + fix.componentInstance.data = fix.componentInstance.semiData; + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.data = Array.from({ length: 100000 }, (_, i) => ({ 'ID': i, 'CompanyName': 'CN' + i })); + fix.detectChanges(); + await wait(500); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + const defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + }); + + it('should keep default height when filtering', fakeAsync(() => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + tick(); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); // initially body height is null in auto-sizing scenarios with empty data + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.data = fix.componentInstance.fullData; + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + let defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + fix.componentInstance.grid.filter('ID', 'ALFKI', IgxStringFilteringOperand.instance().condition('equals')); + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + })); + + it('should not keep default height when lower the amount of bound data', fakeAsync(() => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + tick(); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); // initially body height is null in auto-sizing scenarios with empty data + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.data = fix.componentInstance.fullData; + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + const defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + fix.componentInstance.grid.data = fix.componentInstance.semiData; + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + })); + + it('should not keep auto-sizing when changing height', async () => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); // initially body height is null in auto-sizing scenarios with empty data + expect(fix.componentInstance.grid.calcHeight).toBeNull(); + fix.componentInstance.data = fix.componentInstance.fullData; + await wait(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + let defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBe(510); + expect(fix.componentInstance.grid.calcHeight).toBe(510); + fix.componentInstance.grid.height = '400px'; + await wait(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBeLessThan(400); + expect(defaultHeightNum).toBeGreaterThan(300); + expect(fix.componentInstance.grid.calcHeight).toBeLessThan(400); + expect(fix.componentInstance.grid.calcHeight).toBeGreaterThan(300); + }); + + it('should not auto-size when changing height is determinable', fakeAsync(() => { + const fix = TestBed.createComponent(IgxGridWrappedInContComponent); + fix.componentInstance.outerHeight = 800; + tick(); + fix.detectChanges(); + let defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + let defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBeLessThan(800); + expect(defaultHeightNum).toBeGreaterThan(700); + expect(fix.componentInstance.grid.calcHeight).toBeLessThan(800); + expect(fix.componentInstance.grid.calcHeight).toBeGreaterThan(700); + fix.componentInstance.data = fix.componentInstance.fullData; + tick(); + fix.detectChanges(); + defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; + defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeight).not.toBeNull(); + expect(defaultHeightNum).toBeLessThan(800); + expect(defaultHeightNum).toBeGreaterThan(700); + expect(fix.componentInstance.grid.calcHeight).toBeLessThan(800); + expect(fix.componentInstance.grid.calcHeight).toBeGreaterThan(700); + fix.componentInstance.data = fix.componentInstance.fullData; + })); + it('should render correct columns if after scrolling right container size changes so that all columns become visible.', async () => { const fix = TestBed.createComponent(IgxGridDefaultRenderingComponent); @@ -3832,7 +4026,9 @@ export class IgxGridColumnPercentageWidthComponent extends IgxGridDefaultRenderi ` }) export class IgxGridWrappedInContComponent extends IgxGridTestComponent { - public data = [ + public data = []; + + public fullData = [ { 'ID': 'ALFKI', 'CompanyName': 'Alfreds Futterkiste' }, { 'ID': 'ANATR', 'CompanyName': 'Ana Trujillo Emparedados y helados' }, { 'ID': 'ANTON', 'CompanyName': 'Antonio Moreno Taquería' }, @@ -3862,6 +4058,10 @@ export class IgxGridWrappedInContComponent extends IgxGridTestComponent { { 'ID': 'FRANS', 'CompanyName': 'Franchi S.p.A.' } ]; + public get semiData(): any[] { + return this.fullData.slice(0, 5); + } + public height = null; public paging = false; public pageSize = 5; diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index 0b6a6b866d9..613189d131e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -124,13 +124,6 @@ export class IgxGridComponent extends IgxGridBaseComponent implements IGridDataB public set data(value: any[]) { this._data = value; - if (this._ngAfterViewInitPassed && - this.calcHeight === null && - this.isPercentHeight) { - /* the body should be auto-sized in this case before igxFor renders the whole data */ - const bodyHeight = this.defaultTargetBodyHeight; - this.calcHeight = bodyHeight > 0 ? bodyHeight : null; - } this.summaryService.clearSummaryCache(); if (this.shouldGenerate) { this.setupColumns(); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.crud.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.crud.spec.ts index 978f5f6b7fd..fb3b6132304 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.crud.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.crud.spec.ts @@ -362,6 +362,7 @@ describe('IgxGrid - CRUD operations', () => { template: ` 0 ? bodyHeight : null; - } - private hg_verticalScrollHandler(event) { this.scrollTop = event.target.scrollTop; } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.navigation.spec.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.navigation.spec.ts index 7054e1869ac..77f160c4496 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.navigation.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.navigation.spec.ts @@ -1038,8 +1038,8 @@ describe('IgxHierarchicalGrid Smaller Child Navigation', () => { template: ` - - + + ` diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts index f5ca8bd05e2..0210188d067 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts @@ -498,6 +498,146 @@ describe('IgxHierarchicalGrid Row Islands', () => { })); }); +describe('IgxHierarchicalGrid Children Sizing', () => { + configureTestSuite(); + let fixture; + let hierarchicalGrid: IgxHierarchicalGridComponent; + const TBODY_CLASS = '.igx-grid__tbody-content'; + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + IgxHierarchicalGridSizingComponent + ], + imports: [ + NoopAnimationsModule, IgxHierarchicalGridModule] + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(IgxHierarchicalGridSizingComponent); + fixture.detectChanges(); + hierarchicalGrid = fixture.componentInstance.hgrid; + })); + + it('should create a child grid with null height when its data is unset then set to a number under 10', fakeAsync(() => { + fixture.detectChanges(); + // expansion + const row = hierarchicalGrid.rowList.first as IgxHierarchicalRowComponent; + UIInteractions.clickElement(row.expander); + fixture.detectChanges(); + const childGrids = fixture.debugElement.queryAll(By.css('igx-child-grid-row')); + const childGrid = childGrids[0].query(By.css('igx-hierarchical-grid')).componentInstance; + + const defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(childGrid.calcHeight).toBeNull(); + childGrid.data = fixture.componentInstance.data; + fixture.detectChanges(); + expect(defaultHeight).toBeNull(); + expect(childGrid.calcHeight).toBeNull(); + expect(childGrid.data.length).toEqual(1); + expect(childGrid.rowList.length).toEqual(1); + })); + + it('should create a child grid with auto-size when its data is unset then set to a number above 10', fakeAsync(() => { + fixture.detectChanges(); + // expansion + const row = hierarchicalGrid.rowList.first as IgxHierarchicalRowComponent; + UIInteractions.clickElement(row.expander); + fixture.detectChanges(); + const childGrids = fixture.debugElement.queryAll(By.css('igx-child-grid-row')); + const childGrid = childGrids[0].query(By.css('igx-hierarchical-grid')).componentInstance; + + let defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(childGrid.calcHeight).toBeNull(); + childGrid.data = fixture.componentInstance.fullData; + tick(); + fixture.detectChanges(); + defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBe('510px'); + expect(childGrid.calcHeight).toBe(510); + expect(childGrid.data.length).toEqual(100000); + expect(childGrid.rowList.length).toEqual(11); + })); + + it('should create a child grid with auto-size when its data is unset then set to a number above 10 and height is 50%', fakeAsync(() => { + fixture.componentInstance.childHeight = '50%'; + fixture.detectChanges(); + // expansion + const row = hierarchicalGrid.rowList.first as IgxHierarchicalRowComponent; + UIInteractions.clickElement(row.expander); + fixture.detectChanges(); + const childGrids = fixture.debugElement.queryAll(By.css('igx-child-grid-row')); + const childGrid = childGrids[0].query(By.css('igx-hierarchical-grid')).componentInstance; + + let defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(childGrid.calcHeight).toBeNull(); + childGrid.data = fixture.componentInstance.fullData; + tick(); + fixture.detectChanges(); + defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBe('510px'); + expect(childGrid.calcHeight).toBe(510); + expect(childGrid.data.length).toEqual(100000); + expect(childGrid.rowList.length).toEqual(11); + })); + + it('should create a child grid fixed size when height is set to px', fakeAsync(() => { + fixture.componentInstance.childHeight = '600px'; + fixture.detectChanges(); + // expansion + const row = hierarchicalGrid.rowList.first as IgxHierarchicalRowComponent; + UIInteractions.clickElement(row.expander); + fixture.detectChanges(); + const childGrids = fixture.debugElement.queryAll(By.css('igx-child-grid-row')); + const childGrid = childGrids[0].query(By.css('igx-hierarchical-grid')).componentInstance; + + let defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + let defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeightNum).toBeGreaterThan(500); + expect(defaultHeightNum).toBeLessThan(600); + expect(childGrid.calcHeight).toBeGreaterThan(500); + expect(childGrid.calcHeight).toBeLessThan(600); + expect(childGrid.rowList.length).toEqual(0); + childGrid.data = fixture.componentInstance.fullData; + tick(); + fixture.detectChanges(); + defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + defaultHeightNum = parseInt(defaultHeight, 10); + expect(defaultHeightNum).toBeGreaterThan(500); + expect(defaultHeightNum).toBeLessThan(600); + expect(childGrid.calcHeight).toBeGreaterThan(500); + expect(childGrid.calcHeight).toBeLessThan(600); + expect(childGrid.data.length).toEqual(100000); + expect(childGrid.rowList.length).toEqual(12); + })); + + it('should create a child grid null height regardless of data when height is set to null', fakeAsync(() => { + fixture.componentInstance.childHeight = null; + fixture.detectChanges(); + // expansion + const row = hierarchicalGrid.rowList.first as IgxHierarchicalRowComponent; + UIInteractions.clickElement(row.expander); + fixture.detectChanges(); + const childGrids = fixture.debugElement.queryAll(By.css('igx-child-grid-row')); + const childGrid = childGrids[0].query(By.css('igx-hierarchical-grid')).componentInstance; + + let defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(childGrid.calcHeight).toBeNull(); + childGrid.data = fixture.componentInstance.semiData; + tick(); + fixture.detectChanges(); + defaultHeight = childGrids[0].query(By.css(TBODY_CLASS)).styles.height; + expect(defaultHeight).toBeNull(); + expect(childGrid.calcHeight).toBeNull(); + expect(childGrid.data.length).toEqual(15); + expect(childGrid.rowList.length).toEqual(15); + })); +}); + describe('IgxHierarchicalGrid Remote Scenarios', () => { configureTestSuite(); let fixture: ComponentFixture; @@ -875,3 +1015,33 @@ export class IgxHierarchicalGridColumnsUpdateComponent extends IgxHierarchicalGr } } +@Component({ + template: ` + + + + + + + + ` +}) +export class IgxHierarchicalGridSizingComponent { + public childHeight = '100%'; + public data = [ + { + ID: 1, + ProductName: 'Car' + } + ]; + public fullData = Array.from({ length: 100000 }, (_, i) => ({ 'ID': i, 'ProductName': 'PN' + i })); + public semiData = Array.from({ length: 15 }, (_, i) => ({ 'ID': i, 'ProductName': 'PN' + i })); + + @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent }) + public hgrid: IgxHierarchicalGridComponent; + + @ViewChild('rowIsland', { read: IgxRowIslandComponent }) + public rowIsland: IgxRowIslandComponent; +} + diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts index b0bb00a6751..b91151a7439 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts @@ -77,8 +77,7 @@ describe('IgxTreeGrid Component Tests', () => { tick(); fix.detectChanges(); const defaultHeight = fix.debugElement.query(By.css(TBODY_CLASS)).styles.height; - expect(defaultHeight).not.toBeNull(); - expect(parseInt(defaultHeight, 10)).toBeGreaterThan(200); + expect(defaultHeight).toBeNull(); expect(fix.componentInstance.isVerticalScrollbarVisible()).toBeFalsy(); expect(grid.rowList.length).toEqual(6); })); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index 2b306011fb5..0274305fb42 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -481,6 +481,14 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent implements IGridD return path.reverse(); } + /** + * @hidden @internal + */ + protected getDataBasedBodyHeight(): number { + return !this.flatData || (this.flatData.length < this._defaultTargetRecordNumber) ? + 0 : this.defaultTargetBodyHeight; + } + /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts index 7813021511e..dbb86ed4cce 100644 --- a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts @@ -231,7 +231,7 @@ class DealsSummaryMinMax extends IgxNumberSummaryOperand { } @Component({ template: GridTemplateStrings.declareGrid( - ` [primaryKey]="'ProductID'" [allowFiltering]="true"`, + ` [primaryKey]="'ProductID'" [height]="null" [allowFiltering]="true"`, '', ColumnDefinitions.productDefaultSummaries) }) export class SummaryColumnComponent extends BasicGridComponent { @@ -599,7 +599,7 @@ export class ScrollableGridSearchComponent extends BasicGridSearchComponent { @Component({ template: GridTemplateStrings.declareGrid( - ` columnWidth="200" `, + ` columnWidth="200" [height]="null" `, '', ColumnDefinitions.idNameJobTitleCompany) }) export class GroupableGridSearchComponent extends ScrollableGridSearchComponent {