We are trying to create multi select component with remote data (with paging) and virtual scroll to use it in grid menu filters. Unfortunately, there are few problems:
1) When I pass selected items from parent component everything that is not in loaded items is ignored (no tags presented) even if I set allowCustom to true. Loading entire data set is not an option for us, amount of data is too large.
2) Selected items are shown incorrect. It selects wrong items, seems the problem is in virtual scroll. Is there any possibility (any attribute?) to control selected items in our code?
3) Checkbox sometimes selects more than one item.
Code example:
HTML:
[data]="items"
[valuePrimitive]="true"
[virtual]="virtual"
[filterable]="true"
[allowCustom]="true"
[checkboxes]="true"
[tagMapper]="tagMapper"
[kendoMultiSelectSummaryTag]="3"
[popupSettings]="popupSettings"
[loading]="isLoading"
[autoClose]="false"
valueField="id"
textField="id"
(valueChange)="onChange($event)"
(opened)="onOpened()"
(filterChange)="onFilterChange($event)"
>
<ng-template kendoMultiSelectHeaderTemplate>
<div class="k-grid-header">
<div class="k-grid-header-wrap">
<table class="multi-select-headers-grid">
<thead>
<tr>
<th
*ngFor="let col of columns"
class="k-header"
[style.width.px]="defaultColumnWidth"
>
{{ col.label }}
</th>
</tr>
</thead>
</table>
</div>
</div>
</ng-template>
<ng-template kendoMultiSelectItemTemplate let-item>
<ng-container *ngFor="let col of columns">
<ng-container *ngIf="item">
<span class="k-cell" [style.width.px]="defaultColumnWidth">{{ item[col.propName] }}</span>
</ng-container>
</ng-container>
</ng-template>
<ng-template kendoMultiSelectGroupTagTemplate let-dataItems>
{{ dataItems.length }} more selected
</ng-template>
</kendo-multiselect>
TS:
items: MultiSelectItem[] = [];
isLoading: boolean = false;
popupSettings: PopupSettings = { width: 500 };
readonly defaultColumnWidth = 200;
state: any = {
skip: 0,
take: 10,
};
virtual: VirtualizationSettings = {
itemHeight: 36,
pageSize: 10,
total: 0,
};
private term: string;
private stateChange = new BehaviorSubject<any>(this.state);
private unsubscribe = new Subject();
@Output() selectionChange: Subject<string[]> = new Subject();
@Output() termChange: Subject<Term> = new Subject();
@ViewChild(MultiSelectComponent, { static: false })
multiSelect: MultiSelectComponent;
ngOnInit(): void {
this.stateChange
.pipe(skip(1), debounceTime(50), takeUntil(this.unsubscribe))
.subscribe(state => {
this.state = state;
this.isLoading = true;
this.emitTermChanged();
});
this.dataSource
.pipe(takeUntil(this.unsubscribe))
.subscribe(response => this.onResponse(response));
}
ngOnDestroy(): void {
this.unsubscribe.next();
}
ngAfterViewInit() {
this.multiSelect.tags = this.selectedItems;
console.log('this.multiSelect.tags: ', this.multiSelect.tags);
}
onChange(value) {
this.selectedItems = value;
this.selectionChange.next(this.selectedItems);
}
onOpened(): void {
this.multiSelect.optionsList.pageChange.subscribe(state => {
this.stateChange.next(state);
});
}
onFilterChange(searchTerm: string): void {
this.term = searchTerm;
this.emitTermChanged();
}
tagMapper = (tags: any) => {
return this.selectedItems;
};
private emitTermChanged() {
this.termChange.next({
value: this.term,
offset: this.state.skip,
size: this.state.take,
});
}
private onResponse(response: any): void {
if (this.virtual.total !== response.totalAmount) {
this.items =
response.totalAmount > response.result.length
? response.result.concat(new Array(response.totalAmount - this.state.take))
: [...response.result];
} else {
this.items.splice(this.state.skip, this.state.take, ...response.result);
}
this.virtual.total = response.totalAmount;
this.isLoading = false;
}
}
export interface MultiSelectItem {
id: string;
name: string;
}
Thanks in advance,
Vlad