From 502530398004af6462a19f1305baf8bdc3add575 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Wed, 31 May 2017 17:48:47 +0100 Subject: [PATCH] [ADF-667] selection mode and row styles (#1914) * selection mode and row styles - single/multiple/none selection modes for DataTable component (and Document List) - support for custom row styles (inline and classname values) - fix karma config (material themes) - readme updates - package-lock.json files for NPM5 support - updated DataTable demo to demonstrate selection modes and row styles * remove package lock files --- demo-shell-ng2/app/app.module.ts | 3 +- .../datatable/datatable-demo.component.css | 10 ++ .../datatable/datatable-demo.component.html | 10 ++ .../datatable/datatable-demo.component.ts | 14 ++- .../app/components/files/files.component.html | 10 +- ng2-components/config/karma.conf-all.js | 1 + .../ng2-alfresco-datatable/README.md | 3 + .../datatable/datatable.component.css | 10 +- .../datatable/datatable.component.html | 6 +- .../datatable/datatable.component.spec.ts | 95 ++++++++++++++++++- .../datatable/datatable.component.ts | 80 ++++++++++++---- .../src/data/object-datatable-adapter.ts | 5 +- .../ng2-alfresco-documentlist/README.md | 3 + .../components/document-list.component.html | 3 + .../src/components/document-list.component.ts | 9 ++ 15 files changed, 225 insertions(+), 37 deletions(-) create mode 100644 demo-shell-ng2/app/components/datatable/datatable-demo.component.css diff --git a/demo-shell-ng2/app/app.module.ts b/demo-shell-ng2/app/app.module.ts index adbddcb5b3..e9cce76e20 100644 --- a/demo-shell-ng2/app/app.module.ts +++ b/demo-shell-ng2/app/app.module.ts @@ -17,7 +17,7 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; -import { MdSlideToggleModule, MdInputModule } from '@angular/material'; +import { MdSlideToggleModule, MdInputModule, MdSelectModule } from '@angular/material'; import { CoreModule } from 'ng2-alfresco-core'; import { SearchModule } from 'ng2-alfresco-search'; @@ -64,6 +64,7 @@ import { routing, MdInputModule, MdSlideToggleModule, + MdSelectModule, CoreModule.forRoot(), LoginModule.forRoot(), SearchModule.forRoot(), diff --git a/demo-shell-ng2/app/components/datatable/datatable-demo.component.css b/demo-shell-ng2/app/components/datatable/datatable-demo.component.css new file mode 100644 index 0000000000..cf1e8fe8da --- /dev/null +++ b/demo-shell-ng2/app/components/datatable/datatable-demo.component.css @@ -0,0 +1,10 @@ +alfresco-datatable >>> .custom-row-style.alfresco-datatable__row:focus { + outline-offset: -1px; + outline-width: 1px; + outline-color: green; + outline-style: solid; +} + +alfresco-datatable >>> .custom-row-style.alfresco-datatable__row--selected { + color: green; +} diff --git a/demo-shell-ng2/app/components/datatable/datatable-demo.component.html b/demo-shell-ng2/app/components/datatable/datatable-demo.component.html index 110d0be5cb..a8ff52f43d 100644 --- a/demo-shell-ng2/app/components/datatable/datatable-demo.component.html +++ b/demo-shell-ng2/app/components/datatable/datatable-demo.component.html @@ -1,8 +1,10 @@
Multiselect
+
+

For 'Multiple' selection mode use Cmd (macOS) or Ctrl (Win) to toggle selection of multiple items.

+ + + {{mode.viewValue}} + + +
diff --git a/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts b/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts index 51754681bf..fc22fbf714 100644 --- a/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts +++ b/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts @@ -15,18 +15,28 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { ObjectDataTableAdapter, DataSorting, ObjectDataRow, ObjectDataColumn, DataCellEvent, DataRowActionEvent } from 'ng2-alfresco-datatable'; @Component({ selector: 'datatable-demo', - templateUrl: './datatable-demo.component.html' + templateUrl: './datatable-demo.component.html', + styleUrls: ['./datatable-demo.component.css'] }) export class DataTableDemoComponent { multiselect: boolean = false; data: ObjectDataTableAdapter; + @Input() + selectionMode = 'single'; + + selectionModes = [ + { value: 'none', viewValue: 'None' }, + { value: 'single', viewValue: 'Single' }, + { value: 'multiple', viewValue: 'Multiple' } + ]; + private _imageUrl: string = 'http://placehold.it/140x100'; private _createdBy: any = { name: 'Denys Vuika', diff --git a/demo-shell-ng2/app/components/files/files.component.html b/demo-shell-ng2/app/components/files/files.component.html index ab82dc8ce5..120461c9d6 100644 --- a/demo-shell-ng2/app/components/files/files.component.html +++ b/demo-shell-ng2/app/components/files/files.component.html @@ -32,20 +32,20 @@ - + diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.spec.ts b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.spec.ts index d6694b778f..6c7d4ac155 100644 --- a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.spec.ts +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.spec.ts @@ -18,7 +18,7 @@ import { SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { CoreModule } from 'ng2-alfresco-core'; -import { MdCheckboxChange } from '@angular/material'; +import { MdCheckboxModule, MdCheckboxChange } from '@angular/material'; import { DataTableComponent } from './datatable.component'; import { DataTableCellComponent } from './datatable-cell.component'; import { @@ -26,7 +26,7 @@ import { DataColumn, DataSorting, ObjectDataTableAdapter, - ObjectDataColumn + ObjectDataColumn, ObjectDataRow } from './../../data/index'; describe('DataTable', () => { @@ -39,7 +39,8 @@ describe('DataTable', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - CoreModule.forRoot() + CoreModule.forRoot(), + MdCheckboxModule ], declarations: [ DataTableCellComponent, @@ -52,7 +53,6 @@ describe('DataTable', () => { fixture = TestBed.createComponent(DataTableComponent); dataTable = fixture.componentInstance; element = fixture.debugElement.nativeElement; - //fixture.detectChanges(); }); beforeEach(() => { @@ -66,6 +66,93 @@ describe('DataTable', () => { }; }); + it('should reset selection on mode change', () => { + spyOn(dataTable, 'resetSelection').and.callThrough(); + + dataTable.data = new ObjectDataTableAdapter( + [ + { name: '1' }, + { name: '2' } + ], + [ new ObjectDataColumn({ key: 'name'}) ] + ); + const rows = dataTable.data.getRows(); + rows[0].isSelected = true; + rows[1].isSelected = true; + + expect(rows[0].isSelected).toBeTruthy(); + expect(rows[1].isSelected).toBeTruthy(); + + dataTable.ngOnChanges({ + selectionMode: new SimpleChange(null, 'multiple', false) + }); + + expect(dataTable.resetSelection).toHaveBeenCalled(); + }); + + it('should select only one row with [single] selection mode', () => { + dataTable.selectionMode = 'single'; + dataTable.data = new ObjectDataTableAdapter( + [ + { name: '1' }, + { name: '2' } + ], + [ new ObjectDataColumn({ key: 'name'}) ] + ); + const rows = dataTable.data.getRows(); + + + dataTable.onRowClick(rows[0], null); + expect(rows[0].isSelected).toBeTruthy(); + expect(rows[1].isSelected).toBeFalsy(); + + dataTable.onRowClick(rows[1], null); + expect(rows[0].isSelected).toBeFalsy(); + expect(rows[1].isSelected).toBeTruthy(); + }); + + it('should unselect the row with [single] selection mode', () => { + dataTable.selectionMode = 'single'; + dataTable.data = new ObjectDataTableAdapter( + [ + { name: '1' }, + { name: '2' } + ], + [ new ObjectDataColumn({ key: 'name'}) ] + ); + const rows = dataTable.data.getRows(); + + dataTable.onRowClick(rows[0], null); + expect(rows[0].isSelected).toBeTruthy(); + expect(rows[1].isSelected).toBeFalsy(); + + dataTable.onRowClick(rows[0], null); + expect(rows[0].isSelected).toBeFalsy(); + expect(rows[1].isSelected).toBeFalsy(); + }); + + it('should select multiple rows with [multiple] selection mode', () => { + dataTable.selectionMode = 'multiple'; + dataTable.data = new ObjectDataTableAdapter( + [ + { name: '1' }, + { name: '2' } + ], + [ new ObjectDataColumn({ key: 'name'}) ] + ); + const rows = dataTable.data.getRows(); + + const event = new MouseEvent('click', { + metaKey: true + }); + + dataTable.onRowClick(rows[0], event); + dataTable.onRowClick(rows[1], event); + + expect(rows[0].isSelected).toBeTruthy(); + expect(rows[1].isSelected).toBeTruthy(); + }); + it('should put actions menu to the right by default', () => { dataTable.data = new ObjectDataTableAdapter([], [ {}, diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.ts b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.ts index 106c2f1e86..9e2b8b8f8e 100644 --- a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.ts +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.ts @@ -52,6 +52,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges { @Input() rows: any[] = []; + @Input() + selectionMode: string = 'single'; // none|single|multiple + @Input() multiselect: boolean = false; @@ -70,6 +73,12 @@ export class DataTableComponent implements AfterContentInit, OnChanges { @Input() allowDropFiles: boolean = false; + @Input() + rowStyle: string; + + @Input() + rowStyleClass: string; + @Output() rowClick: EventEmitter = new EventEmitter(); @@ -88,10 +97,6 @@ export class DataTableComponent implements AfterContentInit, OnChanges { noContentTemplate: TemplateRef; isSelectAllChecked: boolean = false; - get selectedRow(): DataRow { - return this.data.selectedRow; - } - constructor(@Optional() private el: ElementRef) { } @@ -111,6 +116,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges { } return; } + + if (changes.selectionMode && !changes.selectionMode.isFirstChange()) { + this.resetSelection(); + } } isPropertyChanged(property: SimpleChange): boolean { @@ -146,25 +155,50 @@ export class DataTableComponent implements AfterContentInit, OnChanges { } } - onRowClick(row: DataRow, e?: Event) { + onRowClick(row: DataRow, e: MouseEvent) { if (e) { e.preventDefault(); } - if (this.data) { - this.data.selectedRow = row; + if (row) { + if (this.data) { + const newValue = !row.isSelected; + const rows = this.data.getRows(); + + if (this.isSingleSelectionMode()) { + rows.forEach(r => r.isSelected = false); + row.isSelected = newValue; + } + + if (this.isMultiSelectionMode()) { + const modifier = e.metaKey || e.ctrlKey; + if (!modifier) { + rows.forEach(r => r.isSelected = false); + } + row.isSelected = newValue; + } + } + + let event = new DataRowEvent(row, e, this); + this.rowClick.emit(event); + + if (!event.defaultPrevented && this.el.nativeElement) { + this.el.nativeElement.dispatchEvent( + new CustomEvent('row-click', { + detail: event, + bubbles: true + }) + ); + } } + } - let event = new DataRowEvent(row, e, this); - this.rowClick.emit(event); - - if (!event.defaultPrevented && this.el.nativeElement) { - this.el.nativeElement.dispatchEvent( - new CustomEvent('row-click', { - detail: event, - bubbles: true - }) - ); + resetSelection(): void { + if (this.data) { + const rows = this.data.getRows(); + if (rows && rows.length > 0) { + rows.forEach(r => r.isSelected = false); + } } } @@ -268,4 +302,16 @@ export class DataTableComponent implements AfterContentInit, OnChanges { rowAllowsDrop(row: DataRow): boolean { return row.isDropTarget === true; } + + hasSelectionMode(): boolean { + return this.isSingleSelectionMode() || this.isMultiSelectionMode(); + } + + isSingleSelectionMode(): boolean { + return this.selectionMode && this.selectionMode.toLowerCase() === 'single'; + } + + isMultiSelectionMode(): boolean { + return this.selectionMode && this.selectionMode.toLowerCase() === 'multiple'; + } } diff --git a/ng2-components/ng2-alfresco-datatable/src/data/object-datatable-adapter.ts b/ng2-components/ng2-alfresco-datatable/src/data/object-datatable-adapter.ts index 8bc358ca6e..52f71497e8 100644 --- a/ng2-components/ng2-alfresco-datatable/src/data/object-datatable-adapter.ts +++ b/ng2-components/ng2-alfresco-datatable/src/data/object-datatable-adapter.ts @@ -196,12 +196,11 @@ export class ObjectDataTableAdapter implements DataTableAdapter { // Simple implementation of the DataRow interface. export class ObjectDataRow implements DataRow { - isSelected: boolean = false; - - constructor(private obj: any) { + constructor(private obj: any, public isSelected: boolean = false) { if (!obj) { throw new Error('Object source not found'); } + } getValue(key: string): any { diff --git a/ng2-components/ng2-alfresco-documentlist/README.md b/ng2-components/ng2-alfresco-documentlist/README.md index ed30263f79..6dcb7d0c26 100644 --- a/ng2-components/ng2-alfresco-documentlist/README.md +++ b/ng2-components/ng2-alfresco-documentlist/README.md @@ -176,6 +176,9 @@ The properties currentFolderId, folderNode and node are the entry initialization | Name | Type | Default | Description | | --- | --- | --- | --- | +| `selectionMode` | string | 'single' | Row selection mode. Can be none, `single` or `multiple`. For `multiple` mode you can use Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows. | +| `rowStyle` | string | | The inline style to apply to every row, see [NgStyle](https://angular.io/docs/ts/latest/api/common/index/NgStyle-directive.html) docs for more details and usage examples | +| `rowStyleClass` | string | | The CSS class to apply to every row | | `currentFolderId` | string | null | Initial node ID of displayed folder. Can be `-root-`, `-shared-`, `-my-`, or a fixed node ID | | `folderNode` | `MinimalNodeEntryEntity` | null | Currently displayed folder node | | `node` | `NodePaging` | null | Document list will show all the node contained in the NodePaging entity | diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.component.html b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.component.html index 196a09e718..07fc6021c6 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.component.html +++ b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.component.html @@ -6,6 +6,7 @@ (permissionErrorEvent)="onPermissionError($event)">