Skip to content

Commit

Permalink
Fixed #13993 - Performance update for virtualScroll selection
Browse files Browse the repository at this point in the history
  • Loading branch information
cetincakiroglu committed Nov 3, 2023
1 parent 3b6ff1f commit 5fd9170
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 68 deletions.
19 changes: 19 additions & 0 deletions src/app/components/listbox/listbox.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ export interface ListboxChangeEvent {
*/
value: any;
}
/**
* Custom change event.
* @see {@link Listbox.onSelectAllChange}
* @group Events
*/
export interface ListboxSelectAllChangeEvent {
/**
* Browser event.
*/
originalEvent: Event;
/**
* Boolean value indicates whether all data is selected.
*/
checked: boolean;
/**
* Method to invoke on model value change.
*/
updateModel?: (value?: any, event?: Event) => void;
}
/**
* Custom filter event.
* @see {@link Listbox.onFilter}
Expand Down
55 changes: 42 additions & 13 deletions src/app/components/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Subscription } from 'rxjs';
import { SearchIcon } from 'primeng/icons/search';
import { CheckIcon } from 'primeng/icons/check';
import { Nullable } from 'primeng/ts-helpers';
import { ListboxChangeEvent, ListboxClickEvent, ListboxDoubleClickEvent, ListboxFilterEvent, ListboxFilterOptions } from './listbox.interface';
import { ListboxChangeEvent, ListboxClickEvent, ListboxDoubleClickEvent, ListboxFilterEvent, ListboxFilterOptions, ListboxSelectAllChangeEvent } from './listbox.interface';
import { Scroller, ScrollerModule } from 'primeng/scroller';

export const LISTBOX_VALUE_ACCESSOR: any = {
Expand Down Expand Up @@ -473,6 +473,16 @@ export class Listbox implements AfterContentInit, OnInit, ControlValueAccessor,
set filterValue(val: string) {
this._filterValue.set(val);
}
/**
* Whether all data is selected.
* @group Props
*/
@Input() get selectAll(): boolean | undefined | null {
return this._selectAll;
}
set selectAll(value: boolean | undefined | null) {
this._selectAll = value;
}
/**
* Callback to invoke on value change.
* @param {ListboxChangeEvent} event - Custom change event.
Expand Down Expand Up @@ -509,6 +519,12 @@ export class Listbox implements AfterContentInit, OnInit, ControlValueAccessor,
* @group Emits
*/
@Output() onBlur: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
/**
* Callback to invoke when all data is selected.
* @param {ListboxSelectAllChangeEvent} event - Custom select event.
* @group Emits
*/
@Output() onSelectAllChange: EventEmitter<ListboxSelectAllChangeEvent> = new EventEmitter<ListboxSelectAllChangeEvent>();

@ViewChild('headerchkbox') headerCheckboxViewChild: Nullable<ElementRef>;

Expand Down Expand Up @@ -630,6 +646,8 @@ export class Listbox implements AfterContentInit, OnInit, ControlValueAccessor,

searchTimeout: any;

_selectAll: boolean | undefined | null = null;

_options = signal<any>(null);

startRangeIndex = signal<number>(-1);
Expand Down Expand Up @@ -744,8 +762,11 @@ export class Listbox implements AfterContentInit, OnInit, ControlValueAccessor,
this.onOptionSelect(null, this.visibleOptions()[this.focusedOptionIndex()]);
}
}

updateModel(value, event?) {
/**
* Updates the model value.
* @group Method
*/
public updateModel(value, event?) {
this.value = value;
this.modelValue.set(value);
this.onModelChange(value);
Expand Down Expand Up @@ -836,21 +857,29 @@ export class Listbox implements AfterContentInit, OnInit, ControlValueAccessor,
}
DomHandler.focus(this.headerCheckboxViewChild.nativeElement);

const value = this.allSelected()
? []
: this.visibleOptions()
.filter((option) => this.isValidOption(option))
.map((option) => this.getOptionValue(option));
this.updateModel(value, event);
this.onChange.emit({ originalEvent: event, value: this.value });
if(this.selectAll !== null) {
this.onSelectAllChange.emit({
originalEvent: event,
checked: !this.allSelected(),
updateModel: this.updateModel.bind(this)
})
} else {
const value = this.allSelected()
? []
: this.visibleOptions()
.filter((option) => this.isValidOption(option))
.map((option) => this.getOptionValue(option));

this.updateModel(value, event);
this.onChange.emit({ originalEvent: event, value: this.value });
}

event.preventDefault();
event.stopPropagation();
// event.stopPropagation();
}

allSelected() {
const allSelected = this.visibleOptions().length > 0 && this.visibleOptions().every((option) => this.isOptionGroup(option) || this.isOptionDisabled(option) || this.isSelected(option));
return ObjectUtils.isNotEmpty(this.visibleOptions()) && allSelected;
return this.selectAll !== null ? this.selectAll : ObjectUtils.isNotEmpty(this.visibleOptions()) && this.visibleOptions().every((option) => this.isOptionGroup(option) || this.isOptionDisabled(option) || this.isSelected(option));
}

onOptionTouchEnd() {
Expand Down
19 changes: 19 additions & 0 deletions src/app/components/multiselect/multiselect.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ export interface MultiSelectChangeEvent {
*/
itemValue?: any;
}
/**
* Custom change event.
* @see {@link MultiSelect.onSelectAllChange}
* @group Events
*/
export interface MultiSelectSelectAllChangeEvent {
/**
* Browser event.
*/
originalEvent: Event;
/**
* Boolean value indicates whether all data is selected.
*/
checked: boolean;
/**
* Method to invoke on model value change.
*/
updateModel?: (value?: any, event?: Event) => void;
}
/**
* Custom filter event.
* @see {@link MultiSelect.onFilter}
Expand Down
72 changes: 54 additions & 18 deletions src/app/components/multiselect/multiselect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
QueryList,
Renderer2,
signal,
SimpleChanges,
TemplateRef,
ViewChild,
ViewEncapsulation
Expand All @@ -41,7 +42,7 @@ import { TimesCircleIcon } from 'primeng/icons/timescircle';
import { TimesIcon } from 'primeng/icons/times';
import { ChevronDownIcon } from 'primeng/icons/chevrondown';
import { Nullable } from 'primeng/ts-helpers';
import { MultiSelectRemoveEvent, MultiSelectFilterOptions, MultiSelectFilterEvent, MultiSelectBlurEvent, MultiSelectChangeEvent, MultiSelectFocusEvent, MultiSelectLazyLoadEvent } from './multiselect.interface';
import { MultiSelectRemoveEvent, MultiSelectFilterOptions, MultiSelectFilterEvent, MultiSelectBlurEvent, MultiSelectChangeEvent, MultiSelectFocusEvent, MultiSelectLazyLoadEvent, MultiSelectSelectAllChangeEvent } from './multiselect.interface';

export const MULTISELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
Expand Down Expand Up @@ -251,7 +252,7 @@ export class MultiSelectItem {
<ng-container *ngIf="allSelected()">
<CheckIcon [styleClass]="'p-checkbox-icon'" *ngIf="!checkIconTemplate" [attr.aria-hidden]="true" />
<span *ngIf="checkIconTemplate" class="p-checkbox-icon" [attr.aria-hidden]="true">
<ng-template *ngTemplateOutlet="checkIconTemplate"></ng-template>
<ng-template *ngTemplateOutlet="checkIconTemplate; context: { $implicit: allSelected() }"></ng-template>
</span>
</ng-container>
</div>
Expand All @@ -268,6 +269,7 @@ export class MultiSelectItem {
[value]="_filterValue() || ''"
(input)="onFilterInputChange($event)"
(keydown)="onFilterKeyDown($event)"
(click)="onInputClick($event)"
(blur)="onFilterBlur($event)"
class="p-multiselect-filter p-inputtext p-component"
[disabled]="disabled"
Expand Down Expand Up @@ -746,6 +748,16 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
this._itemSize = val;
console.warn('The itemSize property is deprecated, use virtualScrollItemSize property instead.');
}
/**
* Whether all data is selected.
* @group Props
*/
@Input() get selectAll(): boolean | undefined | null {
return this._selectAll;
}
set selectAll(value: boolean | undefined | null) {
this._selectAll = value;
}
/**
* Fields used when filtering the options, defaults to optionLabel.
* @group Props
Expand Down Expand Up @@ -823,6 +835,12 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
* @group Emits
*/
@Output() onRemove: EventEmitter<MultiSelectRemoveEvent> = new EventEmitter<MultiSelectRemoveEvent>();
/**
* Callback to invoke when all data is selected.
* @param {MultiSelectSelectAllChangeEvent} event - Custom select event.
* @group Emits
*/
@Output() onSelectAllChange: EventEmitter<MultiSelectSelectAllChangeEvent> = new EventEmitter<MultiSelectSelectAllChangeEvent>();

@ViewChild('container') containerViewChild: Nullable<ElementRef>;

Expand Down Expand Up @@ -852,6 +870,8 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft

searchTimeout: any;

_selectAll: boolean | undefined | null = null;

_autoZIndex: boolean | undefined;

_baseZIndex: number | undefined;
Expand All @@ -868,7 +888,7 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft

_selectionLimit: number | undefined;

public value: any[] | undefined | null;
value: any[];

public _filteredOptions: any[] | undefined | null;

Expand Down Expand Up @@ -1050,7 +1070,7 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
return ObjectUtils.isNotEmpty(this.maxSelectedLabels) && this.modelValue() && this.modelValue().length > this.maxSelectedLabels ? this.modelValue().slice(0, this.maxSelectedLabels) : this.modelValue();
});

constructor(public el: ElementRef, public renderer: Renderer2, public cd: ChangeDetectorRef, public zone: NgZone, public filterService: FilterService, public config: PrimeNGConfig, public overlayService: OverlayService) {}
constructor( public el: ElementRef, public renderer: Renderer2, public cd: ChangeDetectorRef, public zone: NgZone, public filterService: FilterService, public config: PrimeNGConfig, public overlayService: OverlayService) {}

ngOnInit() {
this.id = this.id || UniqueComponentId();
Expand Down Expand Up @@ -1171,17 +1191,27 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
}
}

updateModel(value, event?) {
/**
* Updates the model value.
* @group Method
*/
public updateModel(value, event?) {
this.value = value;
this.onModelChange(value);
this.modelValue.set(value);

this.onChange.emit({
originalEvent: event,
value: value
value: this.value
});
}

onInputClick(event) {
event.stopPropagation();
event.preventDefault();
this.focusedOptionIndex.set(-1);
}

onOptionSelect(event, isFocus = false, index = -1) {
const { originalEvent, option } = event;
if (this.disabled || this.isOptionDisabled(option)) {
Expand Down Expand Up @@ -1297,7 +1327,6 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft

isSelected(option) {
const optionValue = this.getOptionValue(option);

return (this.modelValue() || []).some((value) => ObjectUtils.equals(value, optionValue, this.equalityKey()));
}

Expand Down Expand Up @@ -1630,7 +1659,7 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
}

this.onClick.emit(event);
this.cd.detectChanges();
this.cd.detectChanges()
}

onFirstHiddenFocus(event) {
Expand Down Expand Up @@ -1720,15 +1749,23 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
return;
}

DomHandler.focus(this.headerCheckboxViewChild.nativeElement);

const value = this.allSelected()
if(this.selectAll !== null) {
this.onSelectAllChange.emit({
originalEvent: event,
checked: !this.allSelected(),
updateModel: this.updateModel.bind(this)
})
} else {
const value = this.allSelected()
? []
: this.visibleOptions()
.filter((option) => this.isValidOption(option))
.map((option) => this.getOptionValue(option));
this.updateModel(value, event);
this.onChange.emit({ originalEvent: event, value: this.value });

this.updateModel(value, event)
}

DomHandler.focus(this.headerCheckboxViewChild.nativeElement);
this.headerCheckboxFocus = true;

event.preventDefault();
Expand Down Expand Up @@ -1772,19 +1809,19 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
}
}

writeValue(value: any): void {
public writeValue(value: any): void {
this.value = this.modelValue();
this.updateModel(this.value);
this.checkSelectionLimit();

this.cd.markForCheck();
}

registerOnChange(fn: Function): void {
public registerOnChange(fn: Function): void {
this.onModelChange = fn;
}

registerOnTouched(fn: Function): void {
public registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}

Expand All @@ -1794,8 +1831,7 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
}

allSelected() {
const allSelected = this.visibleOptions().length > 0 && this.visibleOptions().every((option) => this.isOptionGroup(option) || this.isOptionDisabled(option) || this.isSelected(option));
return ObjectUtils.isNotEmpty(this.visibleOptions()) && allSelected;
return this.selectAll !== null ? this.selectAll : ObjectUtils.isNotEmpty(this.visibleOptions()) && this.visibleOptions().every((option) => this.isOptionGroup(option) || this.isOptionDisabled(option) || this.isSelected(option));
}

/**
Expand Down
Loading

0 comments on commit 5fd9170

Please sign in to comment.