Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { AfterViewInit, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { ListViewSelectionMode } from './enums/list-view-selection-mode.enum';
- import { ListViewColumn } from './interfaces/list-view-column.interface';
- import { SearchTextboxComponent } from '../search-textbox/search-textbox.component';
- import { FilterService, SortingService } from 'Common';
- import { ListViewRecordAction } from './interfaces/list-view-record-action.interface';
- import { BehaviorSubject, Observable, Subject, take, takeUntil } from 'rxjs';
- @Component({
- selector: 'list-view',
- standalone: true,
- imports: [
- CommonModule,
- SearchTextboxComponent
- ],
- templateUrl: './list-view.component.html',
- styleUrls: ['./list-view.component.css']
- })
- export class ListViewComponent<T> implements OnInit, OnDestroy, AfterViewInit {
- // Enumerations
- public ListViewSelectionMode = ListViewSelectionMode;
- // Initialization properties (required)
- @Input({ required: true }) public columns!: ListViewColumn<T>[];
- @Input({ required: true }) public source!: Observable<T[]>;
- // View/layout properties
- @Input() public hover: boolean = true;
- @Input() public small: boolean = true;
- @Input() public showHeaderRow: boolean = true;
- @Input() public showSearch: boolean = true;
- @Input() public showStatusBar: boolean = true;
- @Input() public showSummary: boolean = true;
- @Input() public showToolbar: boolean = true;
- // Functional properties
- @Input() public actions?: ListViewRecordAction<T>[];
- @Input() public selectionMode?: ListViewSelectionMode;
- // Selection properties
- @Input() public selectionModel?: Observable<Map<T, boolean>>;
- @Input() public getRecordSelectable?: (record: T) => boolean;
- @Input() public onRecordSelecting?: (record: T, next: boolean) => Observable<boolean>;
- @Output() public onRecordClicked: EventEmitter<T> = new EventEmitter<T>();
- @Output() public onRecordSelected: EventEmitter<T> = new EventEmitter<T>();
- @Output() public onSelectionChanged: EventEmitter<T[]> = new EventEmitter<T[]>();
- // Record management
- public displayedRecords$: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]);
- private selectedRecords: Map<T, boolean> = new Map<T, boolean>();
- private records: T[] = [];
- private filtered?: T[];
- // Private
- private readonly searchProperties: string[] = [];
- private readonly onDestroying$: Subject<void> = new Subject<void>();
- constructor(
- private changeDetector: ChangeDetectorRef,
- private filterService: FilterService,
- private sortingService: SortingService
- ) { }
- public get allDisplayedRecordsAreSelected(): boolean {
- const displayedRecords = this.displayedRecords$.getValue();
- for (let i = 0; i < displayedRecords.length; i++) {
- const record = displayedRecords[i];
- if (!this.selectedRecords.get(record)) {
- return false;
- }
- }
- return true;
- }
- public get displayedColumns(): ListViewColumn<T>[] {
- return this.columns;
- }
- public get displayedRecordCount(): number {
- return this.displayedRecords$.getValue().length;
- }
- public get totalRecordCount(): number {
- return this.records.length;
- }
- public get isMultiSelect(): boolean {
- return this.selectionMode === ListViewSelectionMode.Multiple;
- }
- public ngOnInit(): void {
- // Cache column search properties
- this.columns.filter(column => column.property !== null && column.isSearchable !== false).map(column => column.property!).forEach(property => {
- this.searchProperties.push(property);
- });
- // Subscribe to source stream changes
- this.source.pipe(
- takeUntil(this.onDestroying$)
- ).subscribe({
- next: (records: T[]) => {
- this.records = records;
- if (this.filtered) {
- this.filtered = this.filtered.filter(record => this.records.includes(record));
- }
- this.updateDisplayedRecords();
- }
- });
- // Subscribe to provided selection model
- if (this.selectionModel) {
- this.selectionModel.pipe(
- takeUntil(this.onDestroying$)
- ).subscribe({
- next: (selectionMap: Map<T, boolean>) => {
- this.selectedRecords = selectionMap;
- }
- })
- }
- }
- public ngOnDestroy(): void {
- this.onDestroying$.next();
- this.onDestroying$.complete();
- }
- public ngAfterViewInit(): void {
- this.changeDetector.detectChanges();
- }
- public onSearch(keyword: string|undefined): void {
- if (keyword) {
- this.filtered = this.filterService.filter<T>(this.records, keyword, this.searchProperties);
- } else {
- this.filtered = undefined;
- }
- this.updateDisplayedRecords();
- }
- public onSort(column: ListViewColumn<T>): void {
- // TODO needs to be fixed
- this.records = this.sortingService.sort(this.records, column.property);
- if (this.filtered && this.filtered.length > 0) {
- this.filtered = this.sortingService.sort(this.filtered, column.property);
- }
- this.updateDisplayedRecords();
- }
- public isRecordSelectable(record: T): boolean {
- return this.getRecordSelectable !== undefined ? this.getRecordSelectable(record) : true;
- }
- public isRecordSelected(record: T): boolean {
- return this.selectedRecords.get(record) as boolean;
- }
- public async toggleRecordSelected(record: T, event: Event): Promise<void> {
- if (event) {
- event.stopPropagation();
- //event.preventDefault();
- }
- if (!this.isRecordSelectable(record)) {
- return;
- }
- const next: boolean = !this.isRecordSelected(record);
- await this.setRecordSelected(record, next, true);
- }
- public toggleAllDisplayedRecordsSelected(): void {
- let allSelected: boolean = this.getCurrentlySelectedRecords().length === this.displayedRecordCount;
- const selected = allSelected ? !allSelected : true;
- this.displayedRecords$.getValue().forEach((displayedRecord: T) => {
- this.setRecordSelected(displayedRecord, selected, false, false);
- });
- const currentlySelectedRecords = this.getCurrentlySelectedRecords();
- this.onSelectionChanged.next(currentlySelectedRecords);
- }
- private getCurrentlySelectedRecords(): T[] {
- let records: T[] = [];
- this.selectedRecords.forEach((isSelected: boolean, record: T) => {
- if (isSelected) {
- records.push(record);
- }
- });
- return records;
- }
- private async setRecordSelected(record: T, next: boolean, shouldPrompt: boolean = true, fireEvent: boolean = true): Promise<void> {
- if (this.isRecordSelected(record) == next) {
- // No change
- return;
- }
- let shouldProceed: boolean = false;
- if (shouldPrompt && this.onRecordSelecting !== undefined) {
- await this.onRecordSelecting(record, next).pipe(
- take(1)
- ).subscribe({
- next: (proceed: boolean) => {
- shouldProceed = proceed;
- }
- });
- } else {
- shouldProceed = true;
- }
- if (!shouldProceed) {
- // Update not desired
- return;
- }
- if (!this.isMultiSelect) {
- this.selectedRecords.forEach((value: boolean, key: T) => {
- this.selectedRecords.set(key, key === record);
- });
- } else {
- this.selectedRecords.set(record, next);
- }
- if (fireEvent) {
- if (this.isMultiSelect) {
- this.onSelectionChanged.next(this.getCurrentlySelectedRecords());
- } else {
- this.onRecordSelected.next(record);
- }
- }
- }
- private updateDisplayedRecords(): void {
- this.displayedRecords$.next(this.filtered ?? this.records);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment