[ADF-1115] selection management for DT/DL components (#2100)

* row select/unselect dom events for DT

- new events for datatable
- improved unit tests for empty content placeholders

* improved selection management for DT

* selection management for document list

* fix tests
This commit is contained in:
Denys Vuika
2017-07-19 12:00:03 +01:00
committed by Eugenio Romano
parent 6bde12f770
commit 24bd860d38
13 changed files with 205 additions and 77 deletions

View File

@@ -161,6 +161,7 @@ export class DataTableDemo {
| allowDropFiles | boolean | false | Toggle file drop support for rows (see **ng2-alfresco-core/UploadDirective** for more details) |
| loading | boolean | false | Flag that indicate if the datable is in loading state and need to show the loading template. Read the documentation above to know how to configure a loading template |
| showHeader | boolean | true | Toggles header visibility |
| selection | DataRow[] | [] | Contains selected rows |
### DataColumn Properties
@@ -189,14 +190,17 @@ Here's the list of available properties you can define for a Data Column definit
### DataTable DOM Events
Below are the DOM events raised by DataTable component.
Below are the DOM events raised by DataTable component.
These events bubble up the component tree and can be handled by any parent component.
| Name | Description |
| --- | --- |
| row-click | Emitted when user clicks the row |
| row-dblclick | Emitted when user double-clicks the row |
| row-click | Raised when user clicks a row |
| row-dblclick | Raised when user double-clicks a row |
| row-select | Raised after user selects a row |
| row-unselect | Raised after user unselects a row |
These events are bubbled up the element tree and can be subscribed to from within parent components.
For example:
```html
<root-component (row-click)="onRowClick($event)">

View File

@@ -55,7 +55,10 @@
</td>
<td *ngIf="multiselect">
<md-checkbox [(ngModel)]="row.isSelected"></md-checkbox>
<md-checkbox
[checked]="row.isSelected"
(change)="onCheckboxChange(row, $event)">
</md-checkbox>
</td>
<td *ngFor="let col of data.getColumns()"
class="adf-data-table-cell adf-data-table-cell--{{col.type || 'text'}} {{col.cssClass}}"

View File

@@ -17,7 +17,7 @@
import {
AfterContentInit, Component, ContentChild, DoCheck, ElementRef, EventEmitter, Input,
IterableDiffers, OnChanges, Optional, Output, SimpleChange, SimpleChanges, TemplateRef
IterableDiffers, OnChanges, Output, SimpleChange, SimpleChanges, TemplateRef
} from '@angular/core';
import { MdCheckboxChange } from '@angular/material';
import { AlfrescoTranslationService, DataColumnListComponent } from 'ng2-alfresco-core';
@@ -92,10 +92,11 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
@Input()
loading: boolean = false;
public noContentTemplate: TemplateRef<any>;
public loadingTemplate: TemplateRef<any>;
noContentTemplate: TemplateRef<any>;
loadingTemplate: TemplateRef<any>;
isSelectAllChecked: boolean = false;
selection = new Array<DataRow>();
private clickObserver: Observer<DataRowEvent>;
private click$: Observable<DataRowEvent>;
@@ -108,7 +109,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
private multiClickStreamSub: Subscription;
constructor(translateService: AlfrescoTranslationService,
@Optional() private el: ElementRef,
private elementRef: ElementRef,
private differs: IterableDiffers) {
if (differs) {
this.differ = differs.find([]).create(null);
@@ -173,10 +174,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
this.singleClickStreamSub = singleClickStream.subscribe((obj: DataRowEvent[]) => {
let event: DataRowEvent = obj[0];
let el = obj[0].sender.el;
this.rowClick.emit(event);
if (!event.defaultPrevented && el.nativeElement) {
el.nativeElement.dispatchEvent(
if (!event.defaultPrevented) {
this.elementRef.nativeElement.dispatchEvent(
new CustomEvent('row-click', {
detail: event,
bubbles: true
@@ -192,10 +192,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
this.multiClickStreamSub = multiClickStream.subscribe((obj: DataRowEvent[]) => {
let event: DataRowEvent = obj[0];
let el = obj[0].sender.el;
this.rowDblClick.emit(event);
if (!event.defaultPrevented && el.nativeElement) {
el.nativeElement.dispatchEvent(
if (!event.defaultPrevented) {
this.elementRef.nativeElement.dispatchEvent(
new CustomEvent('row-dblclick', {
detail: event,
bubbles: true
@@ -247,21 +246,32 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
const newValue = !row.isSelected;
const rows = this.data.getRows();
const domEventName = newValue ? 'row-select' : 'row-unselect';
const domEvent = new CustomEvent(domEventName, {
detail: {
row: row,
selection: this.selection
},
bubbles: true
});
if (this.isSingleSelectionMode()) {
rows.forEach(r => r.isSelected = false);
row.isSelected = newValue;
this.resetSelection();
this.selectRow(row, newValue);
this.elementRef.nativeElement.dispatchEvent(domEvent);
}
if (this.isMultiSelectionMode()) {
const modifier = e.metaKey || e.ctrlKey;
if (!modifier) {
rows.forEach(r => r.isSelected = false);
this.resetSelection();
}
row.isSelected = newValue;
this.selectRow(row, newValue);
this.elementRef.nativeElement.dispatchEvent(domEvent);
}
}
let dataRowEvent = new DataRowEvent(row, e, this);
const dataRowEvent = new DataRowEvent(row, e, this);
this.clickObserver.next(dataRowEvent);
}
}
@@ -272,7 +282,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
if (rows && rows.length > 0) {
rows.forEach(r => r.isSelected = false);
}
this.selection.splice(0);
}
this.isSelectAllChecked = false;
}
onRowDblClick(row: DataRow, e?: Event) {
@@ -301,12 +313,29 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
let rows = this.data.getRows();
if (rows && rows.length > 0) {
for (let i = 0; i < rows.length; i++) {
rows[i].isSelected = e.checked;
this.selectRow(rows[i], e.checked);
}
}
}
}
onCheckboxChange(row: DataRow, event: MdCheckboxChange) {
const newValue = event.checked;
this.selectRow(row, newValue);
const domEventName = newValue ? 'row-select' : 'row-unselect';
const domEvent = new CustomEvent(domEventName, {
detail: {
row: row,
selection: this.selection
},
bubbles: true
});
this.elementRef.nativeElement.dispatchEvent(domEvent);
}
onImageLoadingError(event: Event) {
if (event && this.fallbackThumbnail) {
let element = <any> event.target;
@@ -384,4 +413,20 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
return `${row.cssClass} ${this.rowStyleClass}`;
}
private selectRow(row: DataRow, value: boolean) {
if (row) {
row.isSelected = value;
const idx = this.selection.indexOf(row);
if (value) {
if (idx < 0) {
this.selection.push(row);
}
} else {
if (idx > -1) {
this.selection.splice(idx, 1);
}
}
}
}
}

View File

@@ -15,31 +15,42 @@
* limitations under the License.
*/
import { Injector } from '@angular/core';
import { getTestBed, TestBed } from '@angular/core/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { DataTableCellComponent } from '../components/datatable/datatable-cell.component';
import { DataTableComponent } from '../components/datatable/datatable.component';
import { MaterialModule } from '../material.module';
import { LoadingContentTemplateDirective } from './loading-template.directive';
describe('LoadingContentTemplateDirective', () => {
let injector: Injector;
let loadingContentTemplateDirective: LoadingContentTemplateDirective;
beforeEach(() => {
let dataTable: DataTableComponent;
let directive: LoadingContentTemplateDirective;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
CoreModule.forRoot()
],
providers: [
LoadingContentTemplateDirective,
DataTableComponent
declarations: [
DataTableComponent,
DataTableCellComponent,
LoadingContentTemplateDirective
]
});
injector = getTestBed();
loadingContentTemplateDirective = injector.get(LoadingContentTemplateDirective);
}).compileComponents();
}));
beforeEach(() => {
let fixture = TestBed.createComponent(DataTableComponent);
dataTable = fixture.componentInstance;
directive = new LoadingContentTemplateDirective(dataTable);
});
it('is defined', () => {
expect(loadingContentTemplateDirective).toBeDefined();
it('applies template to the datatable', () => {
const template = {};
directive.template = template;
directive.ngAfterContentInit();
expect(dataTable.loadingTemplate).toBe(template);
});
});

View File

@@ -30,7 +30,9 @@ export class LoadingContentTemplateDirective implements AfterContentInit {
}
ngAfterContentInit() {
this.dataTable.loadingTemplate = this.template;
if (this.dataTable) {
this.dataTable.loadingTemplate = this.template;
}
}
}

View File

@@ -15,31 +15,42 @@
* limitations under the License.
*/
import { Injector } from '@angular/core';
import { getTestBed, TestBed } from '@angular/core/testing';
import { async, getTestBed, TestBed } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { DataTableCellComponent } from '../components/datatable/datatable-cell.component';
import { DataTableComponent } from '../components/datatable/datatable.component';
import { MaterialModule } from '../material.module';
import { NoContentTemplateDirective } from './no-content-template.directive';
describe('NoContentTemplateDirective', () => {
let injector: Injector;
let noContentTemplateDirective: NoContentTemplateDirective;
beforeEach(() => {
let dataTable: DataTableComponent;
let directive: NoContentTemplateDirective;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
CoreModule.forRoot()
],
providers: [
NoContentTemplateDirective,
DataTableComponent
declarations: [
DataTableComponent,
DataTableCellComponent,
NoContentTemplateDirective
]
});
injector = getTestBed();
noContentTemplateDirective = injector.get(NoContentTemplateDirective);
}).compileComponents();
}));
beforeEach(() => {
let fixture = TestBed.createComponent(DataTableComponent);
dataTable = fixture.componentInstance;
directive = new NoContentTemplateDirective(dataTable);
});
it('is defined', () => {
expect(noContentTemplateDirective).toBeDefined();
it('applies template to the datatable', () => {
const template = {};
directive.template = template;
directive.ngAfterContentInit();
expect(dataTable.noContentTemplate).toBe(template);
});
});

View File

@@ -30,6 +30,8 @@ export class NoContentTemplateDirective implements AfterContentInit {
}
ngAfterContentInit() {
this.dataTable.noContentTemplate = this.template;
if (this.dataTable) {
this.dataTable.noContentTemplate = this.template;
}
}
}