Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #13993 - Performance update for virtualScroll selection #14013

Merged
merged 3 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 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,21 @@ 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;
}
/**
* Custom filter event.
* @see {@link Listbox.onFilter}
Expand Down
53 changes: 41 additions & 12 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 @@ -745,8 +763,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 @@ -841,20 +862,28 @@ 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);
if(this.selectAll !== null) {
this.onSelectAllChange.emit({
originalEvent: event,
checked: !this.allSelected()
})
} 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
15 changes: 15 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,21 @@ 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;
}
/**
* Custom filter event.
* @see {@link MultiSelect.onFilter}
Expand Down
67 changes: 51 additions & 16 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 @@ -758,6 +760,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 @@ -835,6 +847,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 @@ -864,6 +882,8 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft

searchTimeout: any;

_selectAll: boolean | undefined | null = null;

_autoZIndex: boolean | undefined;

_baseZIndex: number | undefined;
Expand All @@ -880,7 +900,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 @@ -1066,7 +1086,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 @@ -1187,12 +1207,22 @@ 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);
}

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 @@ -1314,7 +1344,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 @@ -1648,7 +1677,7 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
}
this.focusInputViewChild?.nativeElement.focus({ preventScroll: true });
this.onClick.emit(event);
this.cd.detectChanges();
this.cd.detectChanges()
}

onFirstHiddenFocus(event) {
Expand Down Expand Up @@ -1738,15 +1767,22 @@ 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()
})
} 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 @@ -1797,11 +1833,11 @@ export class MultiSelect implements OnInit, AfterViewInit, AfterContentInit, Aft
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 @@ -1811,8 +1847,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
Loading