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"
[panelClass]="{ 'os-search-value-selector': multiple }"
[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>
<ngx-mat-select-search [formControl]="searchValue"></ngx-mat-select-search>
</mat-option>
@ -14,7 +24,7 @@
<mat-chip
*ngFor="let item of selectedItems"
[removable]="true"
(removed)="removeItem(item.id)"
(removed)="removeChipItem(item)"
[disableRipple]="true"
>
{{ item.getTitle() | translate }}
@ -36,8 +46,12 @@
</mat-option>
<mat-divider></mat-divider>
</ng-container>
<cdk-virtual-scroll-viewport class="vscroll-viewport" minBufferPx="200" maxBufferPx="300" [itemSize]="50">
<mat-option *cdkVirtualFor="let selectedItem of getFilteredItems()" [value]="selectedItem.id">
<cdk-virtual-scroll-viewport class="vscroll-viewport" minBufferPx="400" maxBufferPx="600" [itemSize]="50">
<mat-option
*cdkVirtualFor="let selectedItem of getFilteredItems()"
[value]="selectedItem.id"
(onSelectionChange)="onSelectionChange($event)"
>
{{ selectedItem.getTitle() | translate }}
</mat-option>
</cdk-virtual-scroll-viewport>

View File

@ -1,4 +1,5 @@
import { FocusMonitor } from '@angular/cdk/a11y';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import {
ChangeDetectionStrategy,
Component,
@ -12,6 +13,7 @@ import {
ViewEncapsulation
} from '@angular/core';
import { FormBuilder, FormControl, NgControl } from '@angular/forms';
import { MatOptionSelectionChange } from '@angular/material/core';
import { MatFormFieldControl } from '@angular/material/form-field';
import { TranslateService } from '@ngx-translate/core';
@ -19,6 +21,7 @@ import { Observable } from 'rxjs';
import { auditTime } from 'rxjs/operators';
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 { Selectable } from '../selectable';
@ -55,6 +58,9 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
@ViewChild('chipPlaceholder', { static: false })
public chipPlaceholder: ElementRef<HTMLElement>;
@ViewChild(CdkVirtualScrollViewport, { static: true })
public cdkVirtualScrollViewPort: CdkVirtualScrollViewport;
/**
* Decide if this should be a single or multi-select-field
*/
@ -134,6 +140,8 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
*/
private selectableItems: Selectable[];
public selectedIds: number[] = [];
public constructor(
protected translate: TranslateService,
formBuilder: FormBuilder,
@ -144,6 +152,13 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
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.
*
@ -167,15 +182,30 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
}
}
public removeItem(itemId: number): void {
const items = <number[]>this.contentForm.value;
items.splice(
items.findIndex(item => item === itemId),
1
);
this.contentForm.setValue(items);
public removeChipItem(item: Selectable): void {
this.addRemoveId(item.id);
}
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 {
if ((event.target as Element).tagName.toLowerCase() !== 'select') {
// this.element.nativeElement.querySelector('select').focus();
@ -197,5 +227,16 @@ export class SearchValueSelectorComponent extends BaseFormControlComponentDirect
protected updateForm(value: Selectable[] | null): void {
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;
}
}
}