UNPKG

clr-angular-static-fix

Version:

1. Install Clarity Icons package through npm:

333 lines (301 loc) 10.4 kB
/* * Copyright (c) 2016-2018 VMware, Inc. All Rights Reserved. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ import { Injectable, TrackByFunction } from '@angular/core'; import { Observable } from 'rxjs'; import { Subject } from 'rxjs'; import { Subscription } from 'rxjs'; import { FiltersProvider } from './filters'; import { Items } from './items'; let nbSelection: number = 0; export enum SelectionType { None, Single, Multi, } @Injectable() export class Selection { public id: string; private prevSelectionRefs: any[] = []; // Refs of selected items private prevSingleSelectionRef: any; // Ref of single selected item constructor(private _items: Items, private _filters: FiltersProvider) { this.id = 'clr-dg-selection' + nbSelection++; this.subscriptions.push( this._filters.change.subscribe(() => { if (!this._selectable) { return; } this.clearSelection(); }) ); this.subscriptions.push( this._items.allChanges.subscribe(updatedItems => { switch (this.selectionType) { case SelectionType.None: { break; } case SelectionType.Single: { let newSingle: any; const trackBy: TrackByFunction<any> = this._items.trackBy; let selectionUpdated: boolean = false; updatedItems.forEach((item, index) => { const ref = trackBy(index, item); // If one of the updated items is the previously selectedSingle, set it as the new one if (this.prevSingleSelectionRef === ref) { newSingle = item; selectionUpdated = true; } }); // Delete the currentSingle if it doesn't exist anymore if we're using smart datagrids // where we expect all items to be present. // No explicit "delete" is required, since it would still be undefined at this point. // Marking it as selectionUpdated will emit the change when the currentSingle is updated below. if (this._items.smart && !newSingle) { selectionUpdated = true; } // TODO: Discussed this with Eudes and this is fine for now. // But we need to figure out a different pattern for the // child triggering the parent change detection problem. // Using setTimeout for now to fix this. setTimeout(() => { if (selectionUpdated) { this.currentSingle = newSingle; } }, 0); break; } case SelectionType.Multi: { let leftOver: any[] = this.current.slice(); const trackBy: TrackByFunction<any> = this._items.trackBy; let selectionUpdated: boolean = false; // TODO: revisit this when we work on https://github.com/vmware/clarity/issues/2342 // currently, the selection is cleared when filter is applied, so the logic inside // the if statement below results in broken behavior. if (leftOver.length > 0) { updatedItems.forEach((item, index) => { const ref = trackBy(index, item); // Look in current selected refs array if item is selected, and update actual value const selectedIndex = this.prevSelectionRefs.indexOf(ref); if (selectedIndex > -1) { leftOver[selectedIndex] = item; selectionUpdated = true; } }); // Filter out any unmatched items if we're using smart datagrids where we expect all items to be // present if (this._items.smart) { leftOver = leftOver.filter(selected => updatedItems.indexOf(selected) > -1); if (this.current.length !== leftOver.length) { selectionUpdated = true; } } // TODO: Discussed this with Eudes and this is fine for now. // But we need to figure out a different pattern for the // child triggering the parent change detection problem. // Using setTimeout for now to fix this. setTimeout(() => { if (selectionUpdated) { this.current = leftOver; } }, 0); } break; } default: { break; } } }) ); } public clearSelection(): void { this.current.length = 0; this.prevSelectionRefs = []; this.emitChange(); } private _selectionType: SelectionType = SelectionType.None; public get selectionType(): SelectionType { return this._selectionType; } public set selectionType(value: SelectionType) { if (value === this.selectionType) { return; } this._selectionType = value; if (value === SelectionType.None) { delete this.current; } else { this.current = []; } } public rowSelectionMode: boolean = false; private get _selectable(): boolean { return this._selectionType === SelectionType.Multi || this._selectionType === SelectionType.Single; } /** * Ignore items changes in the same change detection cycle. */ private debounce: boolean = false; /** * Subscriptions to the other providers changes. */ private subscriptions: Subscription[] = []; /** * Cleans up our subscriptions to other providers */ public destroy() { this.subscriptions.forEach(sub => sub.unsubscribe()); } /** * The current selection in single selection type */ private _currentSingle: any; public get currentSingle(): any { return this._currentSingle; } public set currentSingle(value: any) { if (value === this._currentSingle) { return; } this._currentSingle = value; if (this._items.all && this._items.trackBy && value) { const lookup = this._items.all.findIndex(maybe => maybe === value); this.prevSingleSelectionRef = this._items.trackBy(lookup, value); } this.emitChange(); // Ignore items changes in the same change detection cycle. // @TODO This can likely be removed! this.debounce = true; setTimeout(() => (this.debounce = false)); } /** * The current selection */ private _current: any[]; public get current(): any[] { return this._current; } public set current(value: any[]) { this._current = value; this.emitChange(); // Ignore items changes in the same change detection cycle. // @TODO This can likely be removed! this.debounce = true; setTimeout(() => (this.debounce = false)); } /** * The Observable that lets other classes subscribe to selection changes */ private _change = new Subject<any[] | any>(); private emitChange() { if (this._selectionType === SelectionType.Single) { this._change.next(this.currentSingle); } else if (this._selectionType === SelectionType.Multi) { this._change.next(this.current); } } // We do not want to expose the Subject itself, but the Observable which is read-only public get change(): Observable<any[] | any> { return this._change.asObservable(); } /** * Checks if an item is currently selected */ public isSelected(item: any): boolean { if (this._selectionType === SelectionType.Single) { return this.currentSingle === item; } else if (this._selectionType === SelectionType.Multi) { return this.current.indexOf(item) >= 0; } return false; } /** * Selects an item */ private selectItem(item: any): void { this.current.push(item); if (this._items.trackBy) { // Push selected ref onto array const lookup = this._items.all.findIndex(maybe => maybe === item); this.prevSelectionRefs.push(this._items.trackBy(lookup, item)); } } /** * Deselects an item */ private deselectItem(indexOfItem: number): void { this.current.splice(indexOfItem, 1); if (this._items.trackBy && indexOfItem < this.prevSelectionRefs.length) { // Keep selected refs array in sync this.prevSelectionRefs.splice(indexOfItem, 1); } } /** * Selects or deselects an item */ public setSelected(item: any, selected: boolean) { switch (this._selectionType) { case SelectionType.None: break; case SelectionType.Single: // in single selection, set currentSingle method should be used break; case SelectionType.Multi: const index = this.current.indexOf(item); if (index >= 0 && !selected) { this.deselectItem(index); this.emitChange(); } else if (index < 0 && selected) { this.selectItem(item); this.emitChange(); } break; default: break; } } /** * Checks if all currently displayed items are selected */ public isAllSelected(): boolean { if (this._selectionType !== SelectionType.Multi || !this._items.displayed) { return false; } const displayedItems: any[] = this._items.displayed; const nbDisplayed = this._items.displayed.length; if (nbDisplayed < 1) { return false; } const temp: any[] = displayedItems.filter(item => this.current.indexOf(item) > -1); return temp.length === displayedItems.length; } /** * Selects or deselects all currently displayed items */ public toggleAll() { if (this._selectionType === SelectionType.None || this._selectionType === SelectionType.Single) { return; } /* * If every currently displayed item is already selected, we clear them. * If at least one item isn't selected, we select every currently displayed item. */ if (this.isAllSelected()) { this._items.displayed.forEach(item => { const currentIndex = this.current.indexOf(item); if (currentIndex > -1) { this.deselectItem(currentIndex); } }); } else { this._items.displayed.forEach(item => { if (this.current.indexOf(item) < 0) { this.selectItem(item); } }); } this.emitChange(); } }