support for column definitions in html (#1705)

* support for column definitions in html

- provides generic support for html-based column definitions for
datatable-like controls

* html column definitions for Task List component

* html column definitions for Document List component

* update code and documentation
This commit is contained in:
Denys Vuika
2017-03-13 11:05:52 +00:00
committed by Mario Romano
parent 5b5916bfb1
commit 57557a991e
17 changed files with 340 additions and 118 deletions

View File

@@ -38,7 +38,15 @@
[data]="dataTasks" [data]="dataTasks"
[landingTaskId]="currentTaskId" [landingTaskId]="currentTaskId"
(rowClick)="onTaskRowClick($event)" (onSuccess)="onSuccessTaskList($event)" (rowClick)="onTaskRowClick($event)" (onSuccess)="onSuccessTaskList($event)"
#activititasklist></activiti-tasklist> #activititasklist>
<!-- Custom column definition demo -->
<!--
<data-columns>
<data-column key="name" title="NAME" class="full-width name-column"></data-column>
<data-column key="created" title="Created" class="hidden"></data-column>
</data-columns>
-->
</activiti-tasklist>
</div> </div>
<div class="mdl-cell mdl-cell--7-col task-column mdl-shadow--2dp"> <div class="mdl-cell mdl-cell--7-col task-column mdl-shadow--2dp">
<activiti-task-details #activitidetails <activiti-task-details #activitidetails

View File

@@ -101,13 +101,7 @@ export class ActivitiDemoComponent implements AfterViewInit {
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private formRenderingService: FormRenderingService, private formRenderingService: FormRenderingService,
private formService: FormService) { private formService: FormService) {
this.dataTasks = new ObjectDataTableAdapter( this.dataTasks = new ObjectDataTableAdapter();
[],
[
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true},
{type: 'text', key: 'created', title: 'Created', cssClass: 'hidden', sortable: true}
]
);
this.dataTasks.setSorting(new DataSorting('created', 'desc')); this.dataTasks.setSorting(new DataSorting('created', 'desc'));
this.dataProcesses = new ObjectDataTableAdapter( this.dataProcesses = new ObjectDataTableAdapter(

View File

@@ -5,6 +5,16 @@
[actions]="true" [actions]="true"
(showRowActionsMenu)="onShowRowActionsMenu($event)" (showRowActionsMenu)="onShowRowActionsMenu($event)"
(executeRowAction)="onExecuteRowAction($event)"> (executeRowAction)="onExecuteRowAction($event)">
<!-- HTML column definition demo -->
<!--
<data-columns>
<data-column type="image" key="icon" [sortable]="false"></data-column>
<data-column key="id" title="Id"></data-column>
<data-column key="createdOn" title="Created"></data-column>
<data-column key="name" title="Name" class="full-width name-column"></data-column>
<data-column key="createdBy.name" title="Created By"></data-column>
</data-columns>
-->
</alfresco-datatable> </alfresco-datatable>
</div> </div>
<div class="p-10"> <div class="p-10">

View File

@@ -21,12 +21,11 @@
(error)="onNavigationError($event)" (error)="onNavigationError($event)"
(success)="resetError()" (success)="resetError()"
(preview)="showFile($event)"> (preview)="showFile($event)">
<content-columns> <data-columns>
<content-column key="$thumbnail" type="image"></content-column> <data-column key="$thumbnail" type="image" [sortable]="false"></data-column>
<content-column <data-column
title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}" title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
key="name" key="name"
sortable="true"
class="full-width ellipsis-cell"> class="full-width ellipsis-cell">
<!-- Example of using custom column template --> <!-- Example of using custom column template -->
<!-- <!--
@@ -34,31 +33,28 @@
<span>Hi! {{entry.data.getValue(entry.row, entry.col)}}</span> <span>Hi! {{entry.data.getValue(entry.row, entry.col)}}</span>
</template> </template>
--> -->
</content-column> </data-column>
<content-column <data-column
title="{{'DOCUMENT_LIST.COLUMNS.TAG' | translate}}" title="{{'DOCUMENT_LIST.COLUMNS.TAG' | translate}}"
key="id" key="id"
sortable="true"
class="full-width ellipsis-cell"> class="full-width ellipsis-cell">
<template let-entry="$implicit"> <template let-entry="$implicit">
<alfresco-tag-node-list [nodeId]="entry.data.getValue(entry.row, entry.col)"></alfresco-tag-node-list> <alfresco-tag-node-list [nodeId]="entry.data.getValue(entry.row, entry.col)"></alfresco-tag-node-list>
</template> </template>
</content-column> </data-column>
<content-column <data-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}" title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
key="createdByUser.displayName" key="createdByUser.displayName"
sortable="true"
class="desktop-only"> class="desktop-only">
</content-column> </data-column>
<content-column <data-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_ON' | translate}}" title="{{'DOCUMENT_LIST.COLUMNS.CREATED_ON' | translate}}"
key="createdAt" key="createdAt"
type="date" type="date"
format="medium" format="medium"
sortable="true"
class="desktop-only"> class="desktop-only">
</content-column> </data-column>
</content-columns> </data-columns>
<content-actions> <content-actions>
<!-- folder actions --> <!-- folder actions -->

View File

@@ -123,8 +123,7 @@ import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist'; import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { CoreModule } from 'ng2-alfresco-core'; import { CoreModule, AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core';
import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core';
import { ObjectDataTableAdapter, DataSorting } from 'ng2-alfresco-datatable'; import { ObjectDataTableAdapter, DataSorting } from 'ng2-alfresco-datatable';
@Component({ @Component({
@@ -176,14 +175,40 @@ export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule);
``` ```
#### Events You can also use HTML-based schema declaration like shown below:
```html
<activiti-tasklist ...>
<data-columns>
<data-column key="name" title="NAME" class="full-width name-column"></data-column>
<data-column key="created" title="Created" class="hidden"></data-column>
</data-columns>
</activiti-tasklist>
```
### DataColumn Properties
Here's the list of available properties you can define for a Data Column definition.
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| key | string | | Data source key, can be either column/property key like `title` or property path like `createdBy.name` |
| type | string (text\|image\|date) | text | Value type |
| format | string | | Value format (if supported by components), for example format of the date |
| sortable | boolean | true | Toggles ability to sort by this column, for example by clicking the column header |
| title | string | | Display title of the column, typically used for column headers |
| template | `TemplateRef` | | Custom column template |
| sr-title | string | | Screen reader title, used for accessibility purposes |
| class | string | | Additional CSS class to be applied to column (header and cells) |
### Events
| Name | Description | | Name | Description |
| --- | --- | | --- | --- |
| `onSuccess` | The event is emitted when the task list is loaded | | `onSuccess` | The event is emitted when the task list is loaded |
| `rowClick` | The event is emitted when the task in the list is clicked | | `rowClick` | The event is emitted when the task in the list is clicked |
#### Options ### Properties
| Name | Description | | Name | Description |
| --- | --- | | --- | --- |

View File

@@ -117,9 +117,9 @@ describe('ActivitiTaskList', () => {
}); });
it('should use the default schemaColumn as default', () => { it('should use the default schemaColumn as default', () => {
component.ngOnInit(); component.ngAfterContentInit();
expect(component.data.getColumns()).toBeDefined(); expect(component.data.getColumns()).toBeDefined();
expect(component.data.getColumns().length).toEqual(4); expect(component.data.getColumns().length).toEqual(2);
}); });
it('should use the schemaColumn passed in input', () => { it('should use the schemaColumn passed in input', () => {
@@ -130,13 +130,13 @@ describe('ActivitiTaskList', () => {
] ]
); );
component.ngOnInit(); component.ngAfterContentInit();
expect(component.data.getColumns()).toBeDefined(); expect(component.data.getColumns()).toBeDefined();
expect(component.data.getColumns().length).toEqual(1); expect(component.data.getColumns().length).toEqual(1);
}); });
it('should return an empty task list when no input parameters are passed', () => { it('should return an empty task list when no input parameters are passed', () => {
component.ngOnInit(); component.ngAfterContentInit();
expect(component.data).toBeDefined(); expect(component.data).toBeDefined();
expect(component.isListEmpty()).toBeTruthy(); expect(component.isListEmpty()).toBeTruthy();
}); });
@@ -177,7 +177,7 @@ describe('ActivitiTaskList', () => {
expect(component.data.getRows()[0].getValue('processDefinitionCategory')).toEqual('http://www.activiti.org/processdef'); expect(component.data.getRows()[0].getValue('processDefinitionCategory')).toEqual('http://www.activiti.org/processdef');
done(); done();
}); });
component.ngOnInit(); component.ngAfterContentInit();
component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment}); component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment});
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -199,7 +199,7 @@ describe('ActivitiTaskList', () => {
done(); done();
}); });
component.ngOnInit(); component.ngAfterContentInit();
component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment}); component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment});
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -221,7 +221,7 @@ describe('ActivitiTaskList', () => {
done(); done();
}); });
component.ngOnInit(); component.ngAfterContentInit();
component.ngOnChanges({'state': state, 'assignment': assignment}); component.ngOnChanges({'state': state, 'assignment': assignment});
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -231,7 +231,7 @@ describe('ActivitiTaskList', () => {
spyOn(taskListService, 'getTasks').and.returnValue(Observable.of(fakeGlobalTask)); spyOn(taskListService, 'getTasks').and.returnValue(Observable.of(fakeGlobalTask));
component.state = 'open'; component.state = 'open';
component.assignment = 'fake-assignee'; component.assignment = 'fake-assignee';
component.ngOnInit(); component.ngAfterContentInit();
component.onSuccess.subscribe((res) => { component.onSuccess.subscribe((res) => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(component.data).toBeDefined(); expect(component.data).toBeDefined();

View File

@@ -15,9 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; import { Component, Input, Output, ContentChild, AfterContentInit, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, LogService, DataColumnListComponent } from 'ng2-alfresco-core';
import { ObjectDataTableAdapter, DataTableAdapter, DataRowEvent, ObjectDataRow } from 'ng2-alfresco-datatable'; import { ObjectDataTableAdapter, DataTableAdapter, DataRowEvent, ObjectDataRow, DataColumn } from 'ng2-alfresco-datatable';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
@@ -29,7 +29,9 @@ declare let componentHandler: any;
templateUrl: './activiti-tasklist.component.html', templateUrl: './activiti-tasklist.component.html',
styleUrls: ['./activiti-tasklist.component.css'] styleUrls: ['./activiti-tasklist.component.css']
}) })
export class ActivitiTaskList implements OnInit, OnChanges { export class ActivitiTaskList implements OnChanges, AfterContentInit {
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
@Input() @Input()
appId: string; appId: string;
@@ -68,11 +70,9 @@ export class ActivitiTaskList implements OnInit, OnChanges {
currentInstanceId: string; currentInstanceId: string;
private defaultSchemaColumn: any[] = [ private defaultSchemaColumn: DataColumn[] = [
{type: 'text', key: 'id', title: 'Id'}, { type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true },
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, { type: 'text', key: 'created', title: 'Created', cssClass: 'hidden', sortable: true }
{type: 'text', key: 'formKey', title: 'Form Key', sortable: true},
{type: 'text', key: 'created', title: 'Created', sortable: true}
]; ];
constructor(private translateService: AlfrescoTranslationService, constructor(private translateService: AlfrescoTranslationService,
@@ -83,9 +83,29 @@ export class ActivitiTaskList implements OnInit, OnChanges {
} }
} }
ngOnInit() { ngAfterContentInit() {
this.setupSchema();
}
/**
* Setup html-based (html definitions) or code behind (data adapter) schema.
* If component is assigned with an empty data adater the default schema settings applied.
*/
setupSchema() {
let schema: DataColumn[] = [];
if (this.columnList && this.columnList.columns && this.columnList.columns.length > 0) {
schema = this.columnList.columns.map(c => <DataColumn> c);
}
if (!this.data) { if (!this.data) {
this.data = this.initDefaultSchemaColumns(); this.data = new ObjectDataTableAdapter([], schema.length > 0 ? schema : this.defaultSchemaColumn);
} else {
if (schema && schema.length > 0) {
this.data.setColumns(schema);
} else if (this.data.getColumns().length === 0) {
this.data.setColumns(this.defaultSchemaColumn);
}
} }
} }
@@ -126,17 +146,6 @@ export class ActivitiTaskList implements OnInit, OnChanges {
this.load(this.requestNode); this.load(this.requestNode);
} }
/**
* Return an initDefaultSchemaColumns instance with the default Schema Column
* @returns {ObjectDataTableAdapter}
*/
initDefaultSchemaColumns(): ObjectDataTableAdapter {
return new ObjectDataTableAdapter(
[],
this.defaultSchemaColumn
);
}
private load(requestNode: TaskQueryRequestRepresentationModel) { private load(requestNode: TaskQueryRequestRepresentationModel) {
this.taskListService.getTotalTasks(requestNode).subscribe( this.taskListService.getTotalTasks(requestNode).subscribe(
(res) => { (res) => {

View File

@@ -39,11 +39,15 @@ import {
NotificationService NotificationService
} from './src/services/index'; } from './src/services/index';
import { DataColumnComponent } from './src/components/data-column/data-column.component';
import { DataColumnListComponent } from './src/components/data-column/data-column-list.component';
import { MATERIAL_DESIGN_DIRECTIVES } from './src/components/material/index'; import { MATERIAL_DESIGN_DIRECTIVES } from './src/components/material/index';
import { CONTEXT_MENU_PROVIDERS, CONTEXT_MENU_DIRECTIVES } from './src/components/context-menu/index'; import { CONTEXT_MENU_PROVIDERS, CONTEXT_MENU_DIRECTIVES } from './src/components/context-menu/index';
export * from './src/services/index'; export * from './src/services/index';
export * from './src/components/index'; export * from './src/components/index';
export * from './src/components/data-column/data-column.component';
export * from './src/components/data-column/data-column-list.component';
export * from './src/utils/index'; export * from './src/utils/index';
export * from './src/events/base.event'; export * from './src/events/base.event';
export * from './src/events/base-ui.event'; export * from './src/events/base-ui.event';
@@ -85,7 +89,9 @@ export function createTranslateLoader(http: Http, logService: LogService) {
], ],
declarations: [ declarations: [
...MATERIAL_DESIGN_DIRECTIVES, ...MATERIAL_DESIGN_DIRECTIVES,
...CONTEXT_MENU_DIRECTIVES ...CONTEXT_MENU_DIRECTIVES,
DataColumnComponent,
DataColumnListComponent
], ],
providers: [ providers: [
...ALFRESCO_CORE_PROVIDERS ...ALFRESCO_CORE_PROVIDERS
@@ -98,7 +104,9 @@ export function createTranslateLoader(http: Http, logService: LogService) {
HttpModule, HttpModule,
TranslateModule, TranslateModule,
...MATERIAL_DESIGN_DIRECTIVES, ...MATERIAL_DESIGN_DIRECTIVES,
...CONTEXT_MENU_DIRECTIVES ...CONTEXT_MENU_DIRECTIVES,
DataColumnComponent,
DataColumnListComponent
] ]
}) })
export class CoreModule { export class CoreModule {

View File

@@ -0,0 +1,29 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, ContentChildren, QueryList } from '@angular/core';
import { DataColumnComponent } from './data-column.component';
@Component({
selector: 'data-columns',
template: ''
})
export class DataColumnListComponent {
@ContentChildren(DataColumnComponent) columns: QueryList<DataColumnComponent>;
}

View File

@@ -0,0 +1,59 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Input, ContentChild, TemplateRef } from '@angular/core';
@Component({
selector: 'data-column',
template: ''
})
export class DataColumnComponent {
@Input()
key: string;
@Input()
type: string = 'text';
@Input()
format: string;
@Input()
sortable: boolean = true;
@Input()
title: string = '';
@ContentChild(TemplateRef)
template: any;
/**
* Title to be used for screen readers.
*/
@Input('sr-title')
srTitle: string;
@Input('class')
cssClass: string;
ngOnInit() {
if (!this.srTitle && this.key === '$thumbnail') {
this.srTitle = 'Thumbnail';
}
}
}

View File

@@ -96,9 +96,7 @@ Follow the 3 steps below:
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) . Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## Basic usage example ## Basic usage
Usage example of this component :
**my.component.ts** **my.component.ts**
@@ -107,18 +105,14 @@ import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { CoreModule } from 'ng2-alfresco-core'; import { CoreModule } from 'ng2-alfresco-core';
import { DataTableModule } from 'ng2-alfresco-datatable'; import { DataTableModule, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { Component } from '@angular/core';
import { CONTEXT_MENU_DIRECTIVES, CONTEXT_MENU_PROVIDERS } from 'ng2-alfresco-core';
import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
@Component({ @Component({
selector: 'alfresco-app-demo', selector: 'alfresco-app-demo',
template: `<alfresco-datatable [data]="data"> template: `
</alfresco-datatable>`, <alfresco-datatable [data]="data">
directives: [ALFRESCO_DATATABLE_DIRECTIVES, CONTEXT_MENU_DIRECTIVES], </alfresco-datatable>
providers: [CONTEXT_MENU_PROVIDERS] `
}) })
export class DataTableDemo { export class DataTableDemo {
data: ObjectDataTableAdapter; data: ObjectDataTableAdapter;
@@ -166,9 +160,23 @@ platformBrowserDynamic().bootstrapModule(AppModule);
![DataTable demo](docs/assets/datatable-demo.png) ![DataTable demo](docs/assets/datatable-demo.png)
### Properties You can also use HTML-based schema declaration like shown below:
| Name | Type | Default | Description ```html
<alfresco-datatable [data]="data" [multiselect]="multiselect">
<data-columns>
<data-column type="image" key="icon" [sortable]="false"></data-column>
<data-column key="id" title="Id"></data-column>
<data-column key="createdOn" title="Created"></data-column>
<data-column key="name" title="Name" class="full-width name-column"></data-column>
<data-column key="createdBy.name" title="Created By"></data-column>
</data-columns>
</alfresco-datatable>
```
### DataTable Properties
| Name | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `data` | DataTableAdapter | instance of **ObjectDataTableAdapter** | data source | | `data` | DataTableAdapter | instance of **ObjectDataTableAdapter** | data source |
| `multiselect` | boolean | false | Toggles multiple row selection, renders checkboxes at the beginning of each row | | `multiselect` | boolean | false | Toggles multiple row selection, renders checkboxes at the beginning of each row |
@@ -177,7 +185,22 @@ platformBrowserDynamic().bootstrapModule(AppModule);
| `fallbackThumbnail` | string | | Fallback image for row ehre thubnail is missing| | `fallbackThumbnail` | string | | Fallback image for row ehre thubnail is missing|
| `contextMenu` | boolean | false | Toggles custom context menu for the component | | `contextMenu` | boolean | false | Toggles custom context menu for the component |
### Events ### DataColumn Properties
Here's the list of available properties you can define for a Data Column definition.
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| key | string | | Data source key, can be either column/property key like `title` or property path like `createdBy.name` |
| type | string (text\|image\|date) | text | Value type |
| format | string | | Value format (if supported by components), for example format of the date |
| sortable | boolean | true | Toggles ability to sort by this column, for example by clicking the column header |
| title | string | | Display title of the column, typically used for column headers |
| template | `TemplateRef` | | Custom column template |
| sr-title | string | | Screen reader title, used for accessibility purposes |
| class | string | | Additional CSS class to be applied to column (header and cells) |
### DataTable Events
| Name | Description | Name | Description
| --- | --- | | --- | --- |
@@ -221,11 +244,13 @@ event: Event // original HTML DOM event
Handler example: Handler example:
```ts ```ts
onRowClicked(event) { onRowClicked(event: DataRowEvent) {
console.log(event.row); console.log(event.row);
} }
``` ```
_This event is cancellable, you can use `event.preventDefault()` to prevent default behaviour._
#### rowDblClick event #### rowDblClick event
_This event is emitted when user double-clicks the row._ _This event is emitted when user double-clicks the row._
@@ -240,11 +265,13 @@ event: Event // original HTML DOM event
Handler example: Handler example:
```ts ```ts
onRowDblClicked(event) { onRowDblClicked(event: DataRowEvent) {
console.log(event.row); console.log(event.row);
} }
``` ```
_This event is cancellable, you can use `event.preventDefault()` to prevent default behaviour._
#### showRowContextMenu event #### showRowContextMenu event
_Emitted before context menu is displayed for a row._ _Emitted before context menu is displayed for a row._
@@ -255,7 +282,7 @@ you can provide all necessary content via handler.
Event properties: Event properties:
```ts ```ts
args: { value: {
row: DataRow, row: DataRow,
col: DataColumn, col: DataColumn,
actions: [] actions: []
@@ -265,14 +292,16 @@ args: {
Handler example: Handler example:
```ts ```ts
onShowRowContextMenu(event) { onShowRowContextMenu(event: DataCellEvent) {
event.args.actions = [ event.value.actions = [
{ ... }, { ... },
{ ... } { ... }
] ]
} }
``` ```
_This event is cancellable, you can use `event.preventDefault()` to prevent default behaviour._
DataTable will automatically render provided menu items. DataTable will automatically render provided menu items.
_Please refer to [ContextMenu](https://www.npmjs.com/package/ng2-alfresco-core) _Please refer to [ContextMenu](https://www.npmjs.com/package/ng2-alfresco-core)
@@ -283,9 +312,20 @@ documentation for more details on context actions format and behavior._
_Emitted before actions menu is displayed for a row. _Emitted before actions menu is displayed for a row.
Requires `actions` property to be set to `true`._ Requires `actions` property to be set to `true`._
Event properties:
```ts
value: {
row: DataRow,
action: any
}
```
Note that DataTable itself does not populate action menu items, Note that DataTable itself does not populate action menu items,
you can provide all necessary content via handler. you can provide all necessary content via handler.
_This event is cancellable, you can use `event.preventDefault()` to prevent default behaviour._
#### executeRowAction event #### executeRowAction event
_Emitted when row action is executed by user._ _Emitted when row action is executed by user._
@@ -346,7 +386,7 @@ a custom `DataTableAdapter` using the following interfaces:
```ts ```ts
interface DataTableAdapter { interface DataTableAdapter {
generateSchema(row: DataRow): col: DataColumn; selectedRow: DataRow;
getRows(): Array<DataRow>; getRows(): Array<DataRow>;
setRows(rows: Array<DataRow>): void; setRows(rows: Array<DataRow>): void;
getColumns(): Array<DataColumn>; getColumns(): Array<DataColumn>;

View File

@@ -95,14 +95,14 @@ describe('DataTable', () => {
it('should initialize default adapter', () => { it('should initialize default adapter', () => {
let table = new DataTableComponent(); let table = new DataTableComponent();
expect(table.data).toBeUndefined(); expect(table.data).toBeUndefined();
table.ngOnInit(); table.ngAfterContentInit();
expect(table.data).toEqual(jasmine.any(ObjectDataTableAdapter)); expect(table.data).toEqual(jasmine.any(ObjectDataTableAdapter));
}); });
it('should initialize with custom data', () => { it('should initialize with custom data', () => {
let data = new ObjectDataTableAdapter([], []); let data = new ObjectDataTableAdapter([], []);
dataTable.data = data; dataTable.data = data;
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
expect(dataTable.data).toBe(data); expect(dataTable.data).toBe(data);
}); });
@@ -130,14 +130,14 @@ describe('DataTable', () => {
it('should prevent default behaviour on row click event', () => { it('should prevent default behaviour on row click event', () => {
let e = jasmine.createSpyObj('event', ['preventDefault']); let e = jasmine.createSpyObj('event', ['preventDefault']);
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
dataTable.onRowClick(null, e); dataTable.onRowClick(null, e);
expect(e.preventDefault).toHaveBeenCalled(); expect(e.preventDefault).toHaveBeenCalled();
}); });
it('should prevent default behaviour on row double-click event', () => { it('should prevent default behaviour on row double-click event', () => {
let e = jasmine.createSpyObj('event', ['preventDefault']); let e = jasmine.createSpyObj('event', ['preventDefault']);
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
dataTable.onRowDblClick(null, e); dataTable.onRowDblClick(null, e);
expect(e.preventDefault).toHaveBeenCalled(); expect(e.preventDefault).toHaveBeenCalled();
}); });
@@ -149,7 +149,7 @@ describe('DataTable', () => {
}); });
it('should not sort if column is missing', () => { it('should not sort if column is missing', () => {
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
let adapter = dataTable.data; let adapter = dataTable.data;
spyOn(adapter, 'setSorting').and.callThrough(); spyOn(adapter, 'setSorting').and.callThrough();
dataTable.onColumnHeaderClick(null); dataTable.onColumnHeaderClick(null);
@@ -157,7 +157,7 @@ describe('DataTable', () => {
}); });
it('should not sort upon clicking non-sortable column header', () => { it('should not sort upon clicking non-sortable column header', () => {
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
let adapter = dataTable.data; let adapter = dataTable.data;
spyOn(adapter, 'setSorting').and.callThrough(); spyOn(adapter, 'setSorting').and.callThrough();
@@ -170,7 +170,7 @@ describe('DataTable', () => {
}); });
it('should set sorting upon column header clicked', () => { it('should set sorting upon column header clicked', () => {
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
let adapter = dataTable.data; let adapter = dataTable.data;
spyOn(adapter, 'setSorting').and.callThrough(); spyOn(adapter, 'setSorting').and.callThrough();
@@ -189,7 +189,7 @@ describe('DataTable', () => {
}); });
it('should invert sorting upon column header clicked', () => { it('should invert sorting upon column header clicked', () => {
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
let adapter = dataTable.data; let adapter = dataTable.data;
let sorting = new DataSorting('column_1', 'asc'); let sorting = new DataSorting('column_1', 'asc');
@@ -226,13 +226,13 @@ describe('DataTable', () => {
let handler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered']); let handler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered']);
window['componentHandler'] = handler; window['componentHandler'] = handler;
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
expect(handler.upgradeAllRegistered).toHaveBeenCalled(); expect(handler.upgradeAllRegistered).toHaveBeenCalled();
}); });
it('should upgrade MDL components only when component handler present', () => { it('should upgrade MDL components only when component handler present', () => {
expect(window['componentHandler']).toBeNull(); expect(window['componentHandler']).toBeNull();
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
}); });
it('should invert "select all" status', () => { it('should invert "select all" status', () => {
@@ -249,7 +249,7 @@ describe('DataTable', () => {
dataTable.data = data; dataTable.data = data;
dataTable.multiselect = true; dataTable.multiselect = true;
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
dataTable.onSelectAllClick(null); dataTable.onSelectAllClick(null);
expect(dataTable.isSelectAllChecked).toBe(true); expect(dataTable.isSelectAllChecked).toBe(true);
@@ -266,7 +266,7 @@ describe('DataTable', () => {
it('should allow "select all" calls with no rows', () => { it('should allow "select all" calls with no rows', () => {
dataTable.multiselect = true; dataTable.multiselect = true;
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
dataTable.onSelectAllClick(null); dataTable.onSelectAllClick(null);
expect(dataTable.isSelectAllChecked).toBe(true); expect(dataTable.isSelectAllChecked).toBe(true);
@@ -278,7 +278,7 @@ describe('DataTable', () => {
dataTable.data = data; dataTable.data = data;
dataTable.multiselect = false; dataTable.multiselect = false;
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
dataTable.onSelectAllClick(null); dataTable.onSelectAllClick(null);
expect(dataTable.isSelectAllChecked).toBe(true); expect(dataTable.isSelectAllChecked).toBe(true);
@@ -354,13 +354,13 @@ describe('DataTable', () => {
}); });
it('should require adapter sorting to evaluate sorting state', () => { it('should require adapter sorting to evaluate sorting state', () => {
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
spyOn(dataTable.data, 'getSorting').and.returnValue(null); spyOn(dataTable.data, 'getSorting').and.returnValue(null);
expect(dataTable.isColumnSorted(<DataColumn> {}, 'asc')).toBeFalsy(); expect(dataTable.isColumnSorted(<DataColumn> {}, 'asc')).toBeFalsy();
}); });
it('should evaluate column sorting state', () => { it('should evaluate column sorting state', () => {
dataTable.ngOnInit(); dataTable.ngAfterContentInit();
spyOn(dataTable.data, 'getSorting').and.returnValue(new DataSorting('column_1', 'asc')); spyOn(dataTable.data, 'getSorting').and.returnValue(new DataSorting('column_1', 'asc'));
expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_1'}, 'asc')).toBeTruthy(); expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_1'}, 'asc')).toBeTruthy();
expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_2'}, 'desc')).toBeFalsy(); expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_2'}, 'desc')).toBeFalsy();

View File

@@ -15,10 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, OnInit, Input, Output, EventEmitter, TemplateRef } from '@angular/core'; import { Component, Input, Output, EventEmitter, TemplateRef, AfterContentInit, ContentChild } from '@angular/core';
import { DataTableAdapter, DataRow, DataColumn, DataSorting, DataRowEvent, ObjectDataTableAdapter } from '../../data/index'; import { DataTableAdapter, DataRow, DataColumn, DataSorting, DataRowEvent, ObjectDataTableAdapter } from '../../data/index';
import { DataCellEvent } from './data-cell.event'; import { DataCellEvent } from './data-cell.event';
import { DataRowActionEvent } from './data-row-action.event'; import { DataRowActionEvent } from './data-row-action.event';
import { DataColumnListComponent } from 'ng2-alfresco-core';
declare var componentHandler; declare var componentHandler;
@@ -28,7 +29,9 @@ declare var componentHandler;
styleUrls: ['./datatable.component.css'], styleUrls: ['./datatable.component.css'],
templateUrl: './datatable.component.html' templateUrl: './datatable.component.html'
}) })
export class DataTableComponent implements OnInit { export class DataTableComponent implements AfterContentInit {
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
@Input() @Input()
data: DataTableAdapter; data: DataTableAdapter;
@@ -70,12 +73,20 @@ export class DataTableComponent implements OnInit {
return this.data.selectedRow; return this.data.selectedRow;
} }
ngOnInit() { ngAfterContentInit() {
if (!this.data) { let schema: DataColumn[] = [];
this.data = new ObjectDataTableAdapter([], []);
if (this.columnList && this.columnList.columns) {
schema = this.columnList.columns.map(c => <DataColumn> c);
} }
// workaround for MDL issues with dynamic components if (!this.data) {
this.data = new ObjectDataTableAdapter([], schema);
} else if (schema && schema.length > 0) {
this.data.setColumns(schema);
}
// workaround for MDL issues with dynamic components
if (componentHandler) { if (componentHandler) {
componentHandler.upgradeAllRegistered(); componentHandler.upgradeAllRegistered();
} }

View File

@@ -51,7 +51,7 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
return schema; return schema;
} }
constructor(data: any[], schema: DataColumn[]) { constructor(data: any[] = [], schema: DataColumn[] = []) {
this._rows = []; this._rows = [];
this._columns = []; this._columns = [];

View File

@@ -392,6 +392,35 @@ A custom set of columns can look like the following:
![Custom columns](docs/assets/custom-columns.png) ![Custom columns](docs/assets/custom-columns.png)
You can also use HTML-based schema declaration used by DataTable, TaskList and other components:
```html
<alfresco-datatable [data]="data" ...>
<data-columns>
<data-column type="image" key="icon" [sortable]="false"></data-column>
<data-column key="id" title="Id"></data-column>
<data-column key="createdOn" title="Created"></data-column>
<data-column key="name" title="Name" class="full-width name-column"></data-column>
<data-column key="createdBy.name" title="Created By"></data-column>
</data-columns>
</alfresco-datatable>
```
#### DataColumn Properties
Here's the list of available properties you can define for a Data Column definition.
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| key | string | | Data source key, can be either column/property key like `title` or property path like `createdBy.name` |
| type | string (text\|image\|date) | text | Value type |
| format | string | | Value format (if supported by components), for example format of the date |
| sortable | boolean | true | Toggles ability to sort by this column, for example by clicking the column header |
| title | string | | Display title of the column, typically used for column headers |
| template | `TemplateRef` | | Custom column template |
| sr-title | string | | Screen reader title, used for accessibility purposes |
| class | string | | Additional CSS class to be applied to column (header and cells) |
DocumentList component assigns an instance of `MinimalNode` type (`alfresco-js-api`) as a data context of each row. DocumentList component assigns an instance of `MinimalNode` type (`alfresco-js-api`) as a data context of each row.
```js ```js

View File

@@ -16,23 +16,13 @@
*/ */
import { import {
Component, Component, OnInit, Input, OnChanges, Output, SimpleChanges, EventEmitter,
OnInit, AfterContentInit, TemplateRef, NgZone, ViewChild, HostListener, ContentChild
Input,
OnChanges,
Output,
SimpleChanges,
EventEmitter,
AfterContentInit,
TemplateRef,
NgZone,
ViewChild,
HostListener
} from '@angular/core'; } from '@angular/core';
import { Subject } from 'rxjs/Rx'; import { Subject } from 'rxjs/Rx';
import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api'; import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api';
import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, DataColumnListComponent } from 'ng2-alfresco-core';
import { DataRowEvent, DataTableComponent, ObjectDataColumn, DataCellEvent, DataRowActionEvent } from 'ng2-alfresco-datatable'; import { DataRowEvent, DataTableComponent, ObjectDataColumn, DataCellEvent, DataRowActionEvent, DataColumn } from 'ng2-alfresco-datatable';
import { DocumentListService } from './../services/document-list.service'; import { DocumentListService } from './../services/document-list.service';
import { ContentActionModel } from './../models/content-action.model'; import { ContentActionModel } from './../models/content-action.model';
import { ShareDataTableAdapter, ShareDataRow, RowFilter, ImageResolver } from './../data/share-datatable-adapter'; import { ShareDataTableAdapter, ShareDataRow, RowFilter, ImageResolver } from './../data/share-datatable-adapter';
@@ -54,6 +44,8 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
baseComponentPath = module.id.replace('components/document-list.component.js', ''); baseComponentPath = module.id.replace('components/document-list.component.js', '');
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
@Input() @Input()
fallbackThumbnail: string = this.baseComponentPath + 'assets/images/ft_ic_miscellaneous.svg'; fallbackThumbnail: string = this.baseComponentPath + 'assets/images/ft_ic_miscellaneous.svg';
@@ -150,7 +142,7 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
private ngZone: NgZone, private ngZone: NgZone,
private translateService: AlfrescoTranslationService) { private translateService: AlfrescoTranslationService) {
this.data = new ShareDataTableAdapter(this.documentListService, this.baseComponentPath, []); this.data = new ShareDataTableAdapter(this.documentListService, this.baseComponentPath);
if (translateService) { if (translateService) {
translateService.addTranslationFolder('ng2-alfresco-documentlist', 'node_modules/ng2-alfresco-documentlist/src'); translateService.addTranslationFolder('ng2-alfresco-documentlist', 'node_modules/ng2-alfresco-documentlist/src');
@@ -193,6 +185,18 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
} }
ngAfterContentInit() { ngAfterContentInit() {
let schema: DataColumn[] = [];
if (this.columnList && this.columnList.columns && this.columnList.columns.length > 0) {
schema = this.columnList.columns.map(c => <DataColumn> c);
}
if (!this.data) {
this.data = new ShareDataTableAdapter(this.documentListService, this.baseComponentPath, schema);
} else if (schema && schema.length > 0) {
this.data.setColumns(schema);
}
let columns = this.data.getColumns(); let columns = this.data.getColumns();
if (!columns || columns.length === 0) { if (!columns || columns.length === 0) {
this.setupDefaultColumns(); this.setupDefaultColumns();

View File

@@ -42,7 +42,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
constructor(private documentListService: DocumentListService, constructor(private documentListService: DocumentListService,
private basePath: string, private basePath: string,
schema: DataColumn[]) { schema: DataColumn[] = []) {
this.rows = []; this.rows = [];
this.columns = schema || []; this.columns = schema || [];
} }