Skip to content

Commit 9a8ee82

Browse files
authored
Merge branch '9.0.x' into mkirova/fix-6654-9.0.x
2 parents c10ab81 + c501480 commit 9a8ee82

File tree

15 files changed

+323
-50
lines changed

15 files changed

+323
-50
lines changed

projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,29 @@ describe('IgxInput', () => {
615615
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
616616
expect(inputGroup.element.nativeElement.classList.contains(INPUT_GROUP_VALID_CSS_CLASS)).toBe(false);
617617
});
618+
619+
it('should not set null or undefined as input value', () => {
620+
const fixture = TestBed.createComponent(InputComponent);
621+
fixture.detectChanges();
622+
623+
const igxInput = fixture.componentInstance.igxInput;
624+
expect(igxInput.value).toBe('');
625+
626+
igxInput.value = undefined;
627+
expect(igxInput.value).toBe('');
628+
629+
igxInput.value = null;
630+
expect(igxInput.value).toBe('');
631+
632+
igxInput.value = 0;
633+
expect(igxInput.value).toBe('0');
634+
635+
igxInput.value = false;
636+
expect(igxInput.value).toBe('false');
637+
638+
igxInput.value = 'Test';
639+
expect(igxInput.value).toBe('Test');
640+
});
618641
});
619642

620643
@Component({ template: `

projects/igniteui-angular/src/lib/directives/input/input.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
7474
*/
7575
@Input('value')
7676
set value(value: any) {
77-
this.nativeElement.value = value;
77+
this.nativeElement.value = value ?? '';
7878
this.checkValidity();
7979
}
8080
/**

projects/igniteui-angular/src/lib/grids/grid-base.directive.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4292,9 +4292,9 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
42924292
/**
42934293
* @hidden
42944294
*/
4295-
protected getPagingHeight(): number {
4295+
protected getPagingFooterHeight(): number {
42964296
let pagingHeight = 0;
4297-
if (this.paging && this.footer) {
4297+
if (this.footer) {
42984298
pagingHeight = this.footer.nativeElement.firstElementChild ?
42994299
this.footer.nativeElement.offsetHeight : 0;
43004300
}
@@ -4325,7 +4325,7 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
43254325
this.theadRow.nativeElement.offsetHeight;
43264326
const footerHeight = this.summariesHeight || this.tfoot.nativeElement.offsetHeight - this.tfoot.nativeElement.clientHeight;
43274327
const toolbarHeight = this.getToolbarHeight();
4328-
const pagingHeight = this.getPagingHeight();
4328+
const pagingHeight = this.getPagingFooterHeight();
43294329
const groupAreaHeight = this.getGroupAreaHeight();
43304330
const renderedHeight = toolbarHeight + actualTheadRow +
43314331
footerHeight + pagingHeight + groupAreaHeight +
@@ -5061,15 +5061,17 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
50615061
let record = {};
50625062
const selectedData = [];
50635063
const activeEl = this.selectionService.activeElement;
5064-
5065-
const selectionMap = Array.from(this.selectionService.selection)
5066-
.filter((tuple) => tuple[0] < source.length);
5064+
const totalItems = (this as any).totalItemCount ?? 0;
5065+
const isRemote = totalItems && totalItems > this.dataView.length;
5066+
const selectionMap = isRemote ? Array.from(this.selectionService.selection) :
5067+
Array.from(this.selectionService.selection).filter((tuple) => tuple[0] < source.length);
50675068

50685069
if (this.cellSelection === GridSelectionMode.single && activeEl) {
50695070
selectionMap.push([activeEl.row, new Set<number>().add(activeEl.column)]);
50705071
}
50715072

5072-
for (const [row, set] of selectionMap) {
5073+
for (let [row, set] of selectionMap) {
5074+
row = isRemote ? row - this.virtualizationState.startIndex : row;
50735075
if (!source[row] || source[row].detailsData !== undefined) {
50745076
continue;
50755077
}

projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,6 +1703,10 @@ describe('IgxGrid Component Tests #grid', () => {
17031703
const footerContent = footer.textContent.trim();
17041704

17051705
expect(footerContent).toEqual('Custom content');
1706+
const grid = fix.componentInstance.grid;
1707+
1708+
const expectedHeight = parseInt(grid.height, 10) - grid.theadRow.nativeElement.offsetHeight - grid.scrollWidth - 100;
1709+
expect(expectedHeight - grid.calcHeight).toBeLessThanOrEqual(1);
17061710
});
17071711
});
17081712

@@ -2090,15 +2094,19 @@ export class IgxGridColumnPercentageWidthComponent extends IgxGridDefaultRenderi
20902094
@Component({
20912095
template:
20922096
`<div>
2093-
<igx-grid #grid [data]="data" [displayDensity]="'compact'" [autoGenerate]="true"
2094-
[paging]="true" [perPage]="5">
2097+
<igx-grid #grid [data]="data" height='300px' [displayDensity]="'compact'" [autoGenerate]="true"
2098+
>
20952099
<igx-grid-footer>
2096-
Custom content
2100+
<div style='height:100px;'>
2101+
Custom content
2102+
</div>
20972103
</igx-grid-footer>
20982104
</igx-grid>
20992105
</div>`
21002106
})
21012107
export class IgxGridWithCustomFooterComponent extends IgxGridTestComponent {
2108+
public data = SampleTestData.foodProductData();
2109+
@ViewChild(IgxGridComponent, { static: true }) public grid: IgxGridComponent;
21022110
}
21032111
@Component({
21042112
template:

projects/igniteui-angular/src/lib/grids/grid/grid.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,11 +1100,11 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType,
11001100
* @inheritdoc
11011101
*/
11021102
getSelectedData(formatters = false, headers = false): any[] {
1103-
if (this.groupingExpressions.length) {
1103+
if (this.groupingExpressions.length || this.hasDetails) {
11041104
const source = [];
11051105

11061106
const process = (record) => {
1107-
if (record.expression || record.summaries) {
1107+
if (record.expression || record.summaries || this.isDetailRecord(record)) {
11081108
source.push(null);
11091109
return;
11101110
}

projects/igniteui-angular/src/lib/grids/grid/grid.master-detail.spec.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, ViewChild, OnInit } from '@angular/core';
2-
import { async, TestBed, ComponentFixture } from '@angular/core/testing';
2+
import { async, TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing';
33
import { configureTestSuite } from '../../test-utils/configure-suite';
44
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
55
import { By } from '@angular/platform-browser';
@@ -799,10 +799,13 @@ describe('IgxGrid Master Detail #grid', () => {
799799
});
800800

801801
describe('Cell Selection', () => {
802-
it('Should exclude expanded detail views when doing range cell selection', () => {
802+
beforeEach(fakeAsync(() => {
803803
fix = TestBed.createComponent(DefaultGridMasterDetailComponent);
804804
grid = fix.componentInstance.grid;
805805
fix.detectChanges();
806+
}));
807+
808+
it('Should exclude expanded detail views when doing range cell selection', fakeAsync(() => {
806809
grid.expandRow(fix.componentInstance.data[2].ID);
807810
const selectionChangeSpy = spyOn<any>(grid.onRangeSelection, 'emit').and.callThrough();
808811
const startCell = grid.getCellByColumn(1, 'ContactName');
@@ -837,7 +840,23 @@ describe('IgxGrid Master Detail #grid', () => {
837840
expect(selectionChangeSpy).toHaveBeenCalledTimes(1);
838841
expect(selectionChangeSpy).toHaveBeenCalledWith(range);
839842
expect(rowDetail.querySelector('[class*="selected"]')).toBeNull();
840-
});
843+
}));
844+
845+
it('getSelectedData should return correct values when there are master details', fakeAsync(() => {
846+
const range = { rowStart: 0, rowEnd: 5, columnStart: 'ContactName', columnEnd: 'ContactName' };
847+
const expectedData = [
848+
{ ContactName: 'Maria Anders' },
849+
{ ContactName: 'Ana Trujillo' },
850+
{ ContactName: 'Antonio Moreno' }
851+
];
852+
grid.expandAll();
853+
tick(100);
854+
fix.detectChanges();
855+
856+
grid.selectRange(range);
857+
fix.detectChanges();
858+
expect(grid.getSelectedData()).toEqual(expectedData);
859+
}));
841860
});
842861

843862
describe('Row Selection', () => {
@@ -1070,10 +1089,12 @@ describe('IgxGrid Master Detail #grid', () => {
10701089
describe('GroupBy', () => {
10711090
beforeEach(async() => {
10721091
fix = TestBed.createComponent(DefaultGridMasterDetailComponent);
1073-
fix.componentInstance.columns[0].hasSummary = true;
10741092
fix.detectChanges();
10751093

10761094
grid = fix.componentInstance.grid;
1095+
grid.getColumnByName('ContactName').hasSummary = true;
1096+
fix.detectChanges();
1097+
10771098
grid.summaryCalculationMode = GridSummaryCalculationMode.childLevelsOnly;
10781099
grid.groupingExpressions =
10791100
[{ fieldName: 'CompanyName', dir: SortingDirection.Asc, ignoreCase: false }];
@@ -1162,9 +1183,7 @@ describe('IgxGrid Master Detail #grid', () => {
11621183
template: `
11631184
<igx-grid [data]="data" [width]="width" [height]="height" [primaryKey]="'ID'" [allowFiltering]='true'
11641185
[paging]="paging" [perPage]="perPage" [rowSelectable]="rowSelectable">
1165-
<igx-column *ngFor="let c of columns" [field]="c.field" [header]="c.field" [width]="c.width" [dataType]='c.dataType'
1166-
[hidden]='c.hidden' [sortable]="c.sortable" [movable]='c.movable' [groupable]='c.groupable' [editable]="c.editable"
1167-
[hasSummary]="c.hasSummary" [pinned]='c.pinned'>
1186+
<igx-column *ngFor="let c of columns" [field]="c.field" [width]="c.width" [dataType]='c.dataType'>
11681187
</igx-column>
11691188
11701189
<ng-template igxGridDetail let-dataItem>
@@ -1203,9 +1222,7 @@ export class DefaultGridMasterDetailComponent {
12031222
template: `
12041223
<igx-grid [data]="data" [expansionStates]='expStates'
12051224
[width]="width" [height]="height" [primaryKey]="'ID'" [paging]="paging" [rowSelectable]="rowSelectable">
1206-
<igx-column *ngFor="let c of columns" [field]="c.field" [header]="c.field" [width]="c.width" [dataType]='c.dataType'
1207-
[hidden]='c.hidden' [sortable]="c.sortable" [movable]='c.movable' [groupable]='c.groupable' [editable]="c.editable"
1208-
[hasSummary]="c.hasSummary" [pinned]='c.pinned'>
1225+
<igx-column *ngFor="let c of columns" [field]="c.field" [header]="c.field" [width]="c.width" [dataType]='c.dataType'>
12091226
</igx-column>
12101227
12111228
<ng-template igxGridDetail let-dataItem>

projects/igniteui-angular/src/lib/select/select.component.ts

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
1-
import { IgxInputDirective, IgxInputState } from './../directives/input/input.directive';
21
import {
3-
Component, ContentChildren, forwardRef, QueryList, ViewChild, Input, ContentChild,
4-
AfterContentInit, HostBinding, Directive, TemplateRef, ElementRef, ChangeDetectorRef, Optional,
5-
Injector, OnInit, AfterViewInit, OnDestroy, Inject, Type
6-
2+
AfterContentInit,
3+
AfterViewInit,
4+
ChangeDetectorRef,
5+
Component,
6+
ContentChild,
7+
ContentChildren,
8+
Directive,
9+
ElementRef,
10+
forwardRef,
11+
HostBinding,
12+
Inject,
13+
Injector,
14+
Input,
15+
OnDestroy,
16+
OnInit,
17+
Optional,
18+
QueryList,
19+
TemplateRef,
20+
ViewChild
721
} from '@angular/core';
8-
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl } from '@angular/forms';
9-
import { Subscription } from 'rxjs';
10-
22+
import { AbstractControl, ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
23+
import { Subscription, Subject } from 'rxjs';
24+
import { takeUntil } from 'rxjs/operators';
25+
import { DisplayDensityToken, IDisplayDensityOptions } from '../core/density';
26+
import { EditorProvider } from '../core/edit-provider';
27+
import { IgxSelectionAPIService } from '../core/selection';
28+
import { CancelableEventArgs } from '../core/utils';
29+
import { IgxLabelDirective } from '../directives/label/label.directive';
30+
import { IGX_DROPDOWN_BASE, ISelectionEventArgs, Navigate } from '../drop-down/drop-down.common';
1131
import { IgxDropDownItemBaseDirective } from '../drop-down/index';
1232
import { IgxInputGroupComponent } from '../input-group/input-group.component';
13-
33+
import { AbsoluteScrollStrategy, OverlaySettings } from '../services/index';
34+
import { IgxInputDirective, IgxInputState } from './../directives/input/input.directive';
1435
import { IgxDropDownComponent } from './../drop-down/drop-down.component';
1536
import { IgxSelectItemComponent } from './select-item.component';
1637
import { SelectPositioningStrategy } from './select-positioning-strategy';
17-
18-
import { OverlaySettings, AbsoluteScrollStrategy } from '../services/index';
19-
import { IGX_DROPDOWN_BASE, ISelectionEventArgs, Navigate } from '../drop-down/drop-down.common';
20-
import { CancelableEventArgs } from '../core/utils';
21-
import { IgxLabelDirective } from '../directives/label/label.directive';
2238
import { IgxSelectBase } from './select.common';
23-
import { EditorProvider } from '../core/edit-provider';
24-
import { IgxSelectionAPIService } from '../core/selection';
25-
import { DisplayDensityToken, IDisplayDensityOptions } from '../core/density';
39+
40+
41+
2642

2743
/** @hidden @internal */
2844
@Directive({
@@ -79,9 +95,9 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
7995
AfterContentInit, OnInit, AfterViewInit, OnDestroy, EditorProvider {
8096

8197
private ngControl: NgControl = null;
82-
private _statusChanges$: Subscription;
8398
private _overlayDefaults: OverlaySettings;
8499
private _value: any;
100+
protected destroy$ = new Subject<boolean>();
85101

86102
/** @hidden @internal do not use the drop-down container class */
87103
public cssClass = false;
@@ -350,11 +366,15 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
350366
scrollStrategy: new AbsoluteScrollStrategy(),
351367
excludePositionTarget: true
352368
};
353-
this.children.changes.subscribe(() => {
369+
const changes$ = this.children.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
354370
this.setSelection(this.items.find(x => x.value === this.value));
355371
this.cdr.detectChanges();
356372
});
357-
Promise.resolve().then(() => this.children.notifyOnChanges());
373+
Promise.resolve().then(() => {
374+
if (!changes$.closed) {
375+
this.children.notifyOnChanges();
376+
}
377+
});
358378
}
359379

360380
/** @hidden @internal */
@@ -393,7 +413,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
393413
public onBlur(): void {
394414
this._onTouchedCallback();
395415
if (this.ngControl && !this.ngControl.valid) {
396-
this.input.valid = IgxInputState.INVALID;
416+
this.input.valid = IgxInputState.INVALID;
397417
} else {
398418
this.input.valid = IgxInputState.INITIAL;
399419
}
@@ -430,7 +450,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
430450
*/
431451
public ngAfterViewInit() {
432452
if (this.ngControl) {
433-
this._statusChanges$ = this.ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
453+
this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged.bind(this));
434454
this.manageRequiredAsterisk();
435455
}
436456
this.cdr.detectChanges();
@@ -440,17 +460,16 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
440460
* @hidden @internal
441461
*/
442462
public ngOnDestroy() {
463+
this.destroy$.next(true);
464+
this.destroy$.complete();
443465
this.selection.clear(this.id);
444-
if (this._statusChanges$) {
445-
this._statusChanges$.unsubscribe();
446-
}
447466
}
448467

449468
/**
450469
* @hidden @internal
451470
* Prevent input blur - closing the items container on Header/Footer Template click.
452471
*/
453-
public mousedownHandler(event) {
472+
public mousedownHandler(event) {
454473
event.preventDefault();
455474
}
456475
}

src/app/app.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ export class AppComponent implements OnInit {
232232
icon: 'view_column',
233233
name: 'Grid Remote Virtualization'
234234
},
235+
{
236+
link: '/gridScrollVirtualization',
237+
icon: 'view_column',
238+
name: 'Grid Remote Virtualization with Scroll'
239+
},
235240
{
236241
link: '/gridRowEdit',
237242
icon: 'view_column',

src/app/app.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ import { GridExternalFilteringComponent } from './grid-external-filtering/grid-e
115115
import { AboutComponent } from './grid-state/about.component';
116116
import { GridSaveStateComponent } from './grid-state/grid-state.component';
117117
import { GridMasterDetailSampleComponent } from './grid-master-detail/grid-master-detail.sample';
118+
import { GridVirtualizationScrollSampleComponent } from './grid-remote-virtualization-with-scroll/grid-remote-virtualization-scroll.sample';
118119

119120
const components = [
120121
AppComponent,
@@ -220,7 +221,8 @@ const components = [
220221
GridFilteringComponent,
221222
GridExternalFilteringComponent,
222223
GridSaveStateComponent,
223-
AboutComponent
224+
AboutComponent,
225+
GridVirtualizationScrollSampleComponent
224226
];
225227

226228
@NgModule({

src/app/app.routing.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import { GridAutoSizeSampleComponent } from './grid-auto-size/grid-auto-size.sam
6868
import { GridSaveStateComponent } from './grid-state/grid-state.component';
6969
import { AboutComponent } from './grid-state/about.component';
7070
import { GridMasterDetailSampleComponent } from './grid-master-detail/grid-master-detail.sample';
71+
import { GridVirtualizationScrollSampleComponent } from './grid-remote-virtualization-with-scroll/grid-remote-virtualization-scroll.sample';
7172

7273
const appRoutes = [
7374
{
@@ -330,6 +331,10 @@ const appRoutes = [
330331
path: 'gridAbout',
331332
component: AboutComponent
332333
},
334+
{
335+
path: 'gridScrollVirtualization',
336+
component: GridVirtualizationScrollSampleComponent
337+
}
333338
];
334339

335340
export const routing = RouterModule.forRoot(appRoutes);

0 commit comments

Comments
 (0)