diff --git a/libs/ngrid-bootstrap/selection-column/README.md b/libs/ngrid-bootstrap/selection-column/README.md new file mode 100644 index 000000000..6ad31151c --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/README.md @@ -0,0 +1 @@ +# Selection implementation using bootstrap diff --git a/libs/ngrid-bootstrap/selection-column/ng-package.json b/libs/ngrid-bootstrap/selection-column/ng-package.json new file mode 100644 index 000000000..da1e3d37d --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/ng-package.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../dist/@pebula/ngrid-bootstrap/selection-column", + "deleteDestPath": false, + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/ngrid-bootstrap/selection-column/package.json b/libs/ngrid-bootstrap/selection-column/package.json new file mode 100644 index 000000000..828e3a40b --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/package.json @@ -0,0 +1,3 @@ +{ + "name": "@pebula/ngrid-bootstrap/selection-column" +} diff --git a/libs/ngrid-bootstrap/selection-column/src/index.ts b/libs/ngrid-bootstrap/selection-column/src/index.ts new file mode 100644 index 000000000..a75b4fe26 --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/src/index.ts @@ -0,0 +1 @@ +export { PblNgridBsSelectionModule } from './lib/bs-selection.module'; diff --git a/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection-plugin.directive.ts b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection-plugin.directive.ts new file mode 100644 index 000000000..70609f1fe --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection-plugin.directive.ts @@ -0,0 +1,108 @@ +import { Directive, Injector, Input, OnDestroy, ComponentFactoryResolver, ComponentRef } from '@angular/core'; + +import { PblNgridComponent, PblNgridPluginController } from '@pebula/ngrid'; + +import { PblNgridBsSelectionComponent } from './bs-selection.component'; + +declare module '@pebula/ngrid/lib/ext/types' { + interface PblNgridPluginExtension { + bsSelectionColumn?: PblNgridBsSelectionPlugin; + } +} + +export const PLUGIN_KEY: 'bsSelectionColumn' = 'bsSelectionColumn'; + +@Directive({ selector: 'pbl-ngrid[bsSelectionColumn]' }) +export class PblNgridBsSelectionPlugin implements OnDestroy { + + @Input() get isCheckboxDisabled() { return this._isCheckboxDisabled; } + set isCheckboxDisabled(value: (row: any) => boolean ) { + if (value !== this._isCheckboxDisabled) { + this._isCheckboxDisabled = value; + if (this.cmpRef && value) { + this.cmpRef.instance.isCheckboxDisabled = value; + this.cmpRef.changeDetectorRef.detectChanges(); + } + } + } + + /** + * Add's a selection column using material's `mat-checkbox` in the column specified. + */ + @Input() get bsSelectionColumn(): string { return this._name; } + set bsSelectionColumn(value: string ) { + if (value !== this._name) { + this._name = value; + if (!value) { + if (this.cmpRef) { + this.cmpRef.destroy(); + this.cmpRef = undefined; + } + } else { + if (!this.cmpRef) { + this.cmpRef = this.cfr.resolveComponentFactory(PblNgridBsSelectionComponent).create(this.injector); + this.cmpRef.instance.table = this.table; + if (this._bulkSelectMode) { + this.cmpRef.instance.bulkSelectMode = this._bulkSelectMode; + } + this.cmpRef.instance.selectionClass = this._selectionClass; + } + if (this.isCheckboxDisabled) { + this.cmpRef.instance.isCheckboxDisabled = this.isCheckboxDisabled; + } + this.cmpRef.instance.name = value; + this.cmpRef.changeDetectorRef.detectChanges(); + } + } + } + + /** + * Defines the behavior when clicking on the bulk select checkbox (header). + * There are 2 options: + * + * - all: Will select all items in the current collection + * - view: Will select only the rendered items in the view + * + * The default value is `all` + */ + @Input() get bulkSelectMode(): 'all' | 'view' | 'none' { return this._bulkSelectMode; } + set bulkSelectMode(value: 'all' | 'view' | 'none') { + if (value !== this._bulkSelectMode) { + this._bulkSelectMode = value; + if (this.cmpRef) { + this.cmpRef.instance.bulkSelectMode = value; + } + } + } + + @Input() get bsSelectionClass(): string { return this._selectionClass; } + set matCheckboxSelectionColor(value: string) { + if (value !== this._selectionClass) { + this._selectionClass = value; + if (this.cmpRef) { + this.cmpRef.instance.selectionClass = value; + } + } + } + + private _name: string; + private _bulkSelectMode: 'all' | 'view' | 'none'; + private _selectionClass: string = ''; + private cmpRef: ComponentRef; + private _removePlugin: (table: PblNgridComponent) => void; + private _isCheckboxDisabled: (row: any) => boolean; + + constructor(private table: PblNgridComponent, + private cfr: ComponentFactoryResolver, + private injector: Injector, + pluginCtrl: PblNgridPluginController) { + this._removePlugin = pluginCtrl.setPlugin(PLUGIN_KEY, this); + } + + ngOnDestroy() { + if (this.cmpRef) { + this.cmpRef.destroy(); + } + this._removePlugin(this.table); + } +} diff --git a/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.html b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.html new file mode 100644 index 000000000..38ce49040 --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.html @@ -0,0 +1,18 @@ + + + + + + +{{ length ? length : '' }} diff --git a/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.scss b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.ts b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.ts new file mode 100644 index 000000000..f49ce927f --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.component.ts @@ -0,0 +1,168 @@ +import { Component, Input, ViewChild, ViewEncapsulation, AfterViewInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { SelectionModel } from '@angular/cdk/collections'; + +import { unrx } from '@pebula/ngrid/core'; +import { + PblNgridComponent, + PblNgridHeaderCellDefDirective, + PblNgridCellDefDirective, + PblNgridFooterCellDefDirective, + PblNgridPluginController, +} from '@pebula/ngrid'; + +const ALWAYS_FALSE_FN = () => false; + +@Component({ + selector: 'pbl-ngrid-bs-checkbox', + templateUrl: './bs-selection.component.html', + styleUrls: ['./bs-selection.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, +}) +export class PblNgridBsSelectionComponent implements AfterViewInit, OnDestroy { + /** + * Unique name for the checkbox column. + * When not set, the name 'checkbox' is used. + * + **/ + @Input() name: string; + + /** + * Defines the behavior when clicking on the bulk select checkbox (header). + * There are 2 options: + * + * - all: Will select all items in the current collection + * - view: Will select only the rendered items in the view + * + * The default value is `all` + */ + @Input() get bulkSelectMode(): 'all' | 'view' | 'none' { return this._bulkSelectMode; } + set bulkSelectMode(value: 'all' | 'view' | 'none') { + if (value !== this._bulkSelectMode) { + this._bulkSelectMode = value; + this.setupSelection(); + } + } + /** + * A Custom selection model, optional. + * If not set, the selection model from the DataSource is used. + */ + @Input() get selection(): SelectionModel { + return this._selection; + } + set selection(value: SelectionModel) { + if (value !== this._selection) { + this._selection = value; + this.setupSelection(); + } + } + + @Input() get isCheckboxDisabled() { return this._isCheckboxDisabled; } + set isCheckboxDisabled(value: (row: any) => boolean) { + if (value !== this._isCheckboxDisabled) { + this._isCheckboxDisabled = value; + if (!this._isCheckboxDisabled || typeof this._isCheckboxDisabled !== 'function') { + this._isCheckboxDisabled = ALWAYS_FALSE_FN; + } + } + } + + @Input() get selectionClass(): string { return this._selectionClass; } + set selectionClass(value: string) { + if (value !== this._selectionClass) { + this._selectionClass = value; + if (this.table.isInit) { + this.markAndDetect(); + } + } + } + + @ViewChild(PblNgridHeaderCellDefDirective, { static: true }) headerDef: PblNgridHeaderCellDefDirective; + @ViewChild(PblNgridCellDefDirective, { static: true }) cellDef: PblNgridCellDefDirective; + @ViewChild(PblNgridFooterCellDefDirective, { static: true }) footerDef: PblNgridFooterCellDefDirective; + + allSelected = false; + length: number; + + private _selection: SelectionModel; + private _bulkSelectMode: 'all' | 'view' | 'none'; + private _isCheckboxDisabled: (row: any) => boolean = ALWAYS_FALSE_FN; + private _selectionClass: string; + + constructor(public table: PblNgridComponent, private cdr: ChangeDetectorRef) { + const pluginCtrl = PblNgridPluginController.find(table); + pluginCtrl.events + .pipe(unrx(this)) + .subscribe( e => { + if (e.kind === 'onDataSource') { + this.selection = e.curr.selection; + } + }); + + } + + ngAfterViewInit(): void { + if (!this.selection && this.table.ds) { + this.selection = this.table.ds.selection; + } + + const registry = this.table.registry; + registry.addMulti('headerCell', this.headerDef); + registry.addMulti('tableCell', this.cellDef); + registry.addMulti('footerCell', this.footerDef); + } + + ngOnDestroy(): void { + unrx.kill(this); + } + + masterToggle(): void { + if (this.allSelected) { + this.selection.clear(); + } else { + const selected = this.getCollection().filter(data => !this._isCheckboxDisabled(data)); + this.selection.select(...selected); + } + } + + rowItemChange(row: any): void { + this.selection.toggle(row); + this.markAndDetect(); + } + + onInput(a,b){ + console.log(a,b) + } + private getCollection() { + const { ds } = this.table; + return this.bulkSelectMode === 'view' ? ds.renderedData : ds.source; + } + + private setupSelection(): void { + unrx.kill(this, this.table); + if (this._selection) { + this.length = this.selection.selected.length; + this.selection.changed + .pipe(unrx(this, this.table)) + .subscribe(() => this.handleSelectionChanged()); + const changeSource = this.bulkSelectMode === 'view' ? this.table.ds.onRenderedDataChanged : this.table.ds.onSourceChanged; + changeSource + .pipe(unrx(this, this.table)) + .subscribe(() => this.handleSelectionChanged()); + } else { + this.length = 0; + } + } + + private handleSelectionChanged() { + const { length } = this.getCollection().filter(data => !this._isCheckboxDisabled(data)); + this.allSelected = !this.selection.isEmpty() && this.selection.selected.length === length; + this.length = this.selection.selected.length; + this.markAndDetect(); + } + + private markAndDetect() { + this.cdr.markForCheck(); + this.cdr.detectChanges(); + } +} diff --git a/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.module.ts b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.module.ts new file mode 100644 index 000000000..3c590abeb --- /dev/null +++ b/libs/ngrid-bootstrap/selection-column/src/lib/bs-selection.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatCheckboxModule } from '@angular/material/checkbox'; + +import { PblNgridModule, ngridPlugin } from '@pebula/ngrid'; +import { PblNgridBsSelectionPlugin, PLUGIN_KEY } from './bs-selection-plugin.directive'; +import { PblNgridBsSelectionComponent } from './bs-selection.component'; + +@NgModule({ + imports: [ CommonModule, MatCheckboxModule, PblNgridModule ], + declarations: [ PblNgridBsSelectionPlugin, PblNgridBsSelectionComponent ], + exports: [ PblNgridBsSelectionPlugin, PblNgridBsSelectionComponent ], + // TODO(REFACTOR_REF 2): remove when ViewEngine is no longer supported by angular (V12 ???) + entryComponents: [ PblNgridBsSelectionComponent ] +}) +export class PblNgridBsSelectionModule { + static readonly NGRID_PLUGIN = ngridPlugin({ id: PLUGIN_KEY }, PblNgridBsSelectionPlugin); +}