Fix a bug where vscroll select lists lost content

Large virtual scroll select lists lost the
content after scrolling. Form values
were removed from the component if the
buffer was exceeding the view
This commit is contained in:
Sean 2020-11-06 18:33:13 +01:00
parent 5b84bddc2a
commit 2364ed66ff
2 changed files with 65 additions and 10 deletions

View File

@ -3,7 +3,17 @@
[multiple]="multiple" [multiple]="multiple"
[panelClass]="{ 'os-search-value-selector': multiple }" [panelClass]="{ 'os-search-value-selector': multiple }"
[errorStateMatcher]="errorStateMatcher" [errorStateMatcher]="errorStateMatcher"
(openedChange)="openSelect($event)"
> >
<!-- Custom display of selected items -->
<mat-select-trigger>
<ng-container *ngIf="selectedItems?.length">
<span *ngFor="let item of selectedItems; let i = index">
{{ item.getTitle() | translate }}<span *ngIf="i < selectedItems.length - 1">, </span>
</span>
</ng-container>
</mat-select-trigger>
<mat-option> <mat-option>
<ngx-mat-select-search [formControl]="searchValue"></ngx-mat-select-search> <ngx-mat-select-search [formControl]="searchValue"></ngx-mat-select-search>
</mat-option> </mat-option>
@ -14,7 +24,7 @@
<mat-chip <mat-chip
*ngFor="let item of selectedItems" *ngFor="let item of selectedItems"
[removable]="true" [removable]="true"
(removed)="removeItem(item.id)" (removed)="removeChipItem(item)"
[disableRipple]="true" [disableRipple]="true"
> >
{{ item.getTitle() | translate }} {{ item.getTitle() | translate }}
@ -36,8 +46,12 @@
</mat-option> </mat-option>
<mat-divider></mat-divider> <mat-divider></mat-divider>
</ng-container> </ng-container>
<cdk-virtual-scroll-viewport class="vscroll-viewport" minBufferPx="200" maxBufferPx="300" [itemSize]="50"> <cdk-virtual-scroll-viewport class="vscroll-viewport" minBufferPx="400" maxBufferPx="600" [itemSize]="50">
<mat-option *cdkVirtualFor="let selectedItem of getFilteredItems()" [value]="selectedItem.id"> <mat-option
*cdkVirtualFor="let selectedItem of getFilteredItems()"
[value]="selectedItem.id"
(onSelectionChange)="onSelectionChange($event)"
>
{{ selectedItem.getTitle() | translate }} {{ selectedItem.getTitle() | translate }}
</mat-option> </mat-option>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>

View File

@ -1,4 +1,5 @@
import { FocusMonitor } from '@angular/cdk/a11y'; import { FocusMonitor } from '@angular/cdk/a11y';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@ -12,6 +13,7 @@ import {
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, FormControl, NgControl } from '@angular/forms'; import { FormBuilder, FormControl, NgControl } from '@angular/forms';
import { MatOptionSelectionChange } from '@angular/material/core';
import { MatFormFieldControl } from '@angular/material/form-field'; import { MatFormFieldControl } from '@angular/material/form-field';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -19,6 +21,7 @@ import { Observable } from 'rxjs';
import { auditTime } from 'rxjs/operators'; import { auditTime } from 'rxjs/operators';
import { BaseFormControlComponentDirective } from 'app/shared/models/base/base-form-control'; import { BaseFormControlComponentDirective } from 'app/shared/models/base/base-form-control';
import { Identifiable } from 'app/shared/models/base/identifiable';
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher'; import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
import { Selectable } from '../selectable'; import { Selectable } from '../selectable';
@ -55,6 +58,9 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
@ViewChild('chipPlaceholder', { static: false }) @ViewChild('chipPlaceholder', { static: false })
public chipPlaceholder: ElementRef<HTMLElement>; public chipPlaceholder: ElementRef<HTMLElement>;
@ViewChild(CdkVirtualScrollViewport, { static: true })
public cdkVirtualScrollViewPort: CdkVirtualScrollViewport;
/** /**
* Decide if this should be a single or multi-select-field * Decide if this should be a single or multi-select-field
*/ */
@ -134,6 +140,8 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
*/ */
private selectableItems: Selectable[]; private selectableItems: Selectable[];
public selectedIds: number[] = [];
public constructor( public constructor(
protected translate: TranslateService, protected translate: TranslateService,
formBuilder: FormBuilder, formBuilder: FormBuilder,
@ -144,6 +152,13 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
super(formBuilder, focusMonitor, element, ngControl); super(formBuilder, focusMonitor, element, ngControl);
} }
public openSelect(event: boolean): void {
if (event) {
this.cdkVirtualScrollViewPort.scrollToIndex(0);
this.cdkVirtualScrollViewPort.checkViewportSize();
}
}
/** /**
* Function to get a list filtered by the entered search value. * Function to get a list filtered by the entered search value.
* *
@ -167,15 +182,30 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
} }
} }
public removeItem(itemId: number): void { public removeChipItem(item: Selectable): void {
const items = <number[]>this.contentForm.value; this.addRemoveId(item.id);
items.splice(
items.findIndex(item => item === itemId),
1
);
this.contentForm.setValue(items);
} }
private addRemoveId(item: number): void {
const idx = this.selectedIds.indexOf(item);
if (idx > -1) {
this.selectedIds.splice(idx, 1);
} else {
this.selectedIds.push(item);
}
this.contentForm.setValue(this.selectedIds);
}
public onSelectionChange(change: MatOptionSelectionChange): void {
if (change.isUserInput) {
const value = change.source.value;
this.addRemoveId(value);
}
}
/**
* Satisfy parent
*/
public onContainerClick(event: MouseEvent): void { public onContainerClick(event: MouseEvent): void {
if ((event.target as Element).tagName.toLowerCase() !== 'select') { if ((event.target as Element).tagName.toLowerCase() !== 'select') {
// this.element.nativeElement.querySelector('select').focus(); // this.element.nativeElement.querySelector('select').focus();
@ -197,5 +227,16 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
protected updateForm(value: Selectable[] | null): void { protected updateForm(value: Selectable[] | null): void {
this.contentForm.setValue(value); this.contentForm.setValue(value);
if (value?.length) {
/**
* Hack:
* for loaded or preselected form, add existing values to selected IDs.
* These are usually always numbers,
* Would be easier to absolutely always use Selectable and never use IDs,
* Could save some work, but every second form has to change for that.
* -> os4 todo
*/
this.selectedIds = value as any;
}
} }
} }