[ADF-716] Task Header - Use a custom view inside the component (#1967)

* Use a generic custom view inside the task header

* Move the component into core component and change name

* Missing file

* Fix unit test

* fix unit test component name
This commit is contained in:
Maurizio Vitale
2017-06-15 17:54:39 +01:00
committed by Eugenio Romano
parent 6393ecbff5
commit d787160c26
15 changed files with 364 additions and 32 deletions

View File

@@ -199,6 +199,43 @@ You can use inside the filter one of the following property
}
```
## ADF Card View
The component shows the [CardViewModel](#cardviewmodel)} object.
```html
<adf-card-view
[properties]="[{label: 'My Label', value: 'My value'}]">
</adf-card-view>
```
### Properties
| Name | Type | Description |
| --- | --- | --- |
| properties | {array[CardViewModel](#cardviewmodel)} | (**required**) The custom view to render |
### CardViewModel
```json
{
"label": "string",
"value": "any",
"format": "string",
"default": "string"
}
```
| Name | Type | Description |
| --- | --- | --- |
| label | string | The label to render |
| value | string | The value to render |
| format | string | The format to use in case the value is a date |
| default | string | The default value to render in case the value is empty |
![adf-custom-view](docs/assets/adf-custom-view.png)
## Activiti Filter
The component shows all the available filters.

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -1,20 +1,7 @@
<div *ngIf="taskDetails">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--4-col" data-automation-id="header-assignee">
<span class="activiti-task-header__label">{{ 'TASK_DETAILS.LABELS.ASSIGNEE' | translate }}: </span>
<span class="activiti-task-header__value" *ngIf="hasAssignee()">{{ taskDetails.assignee.firstName }} {{ taskDetails.assignee.lastName }}</span>
<span class="activiti-task-header__value" *ngIf="!hasAssignee()">{{ 'TASK_DETAILS.ASSIGNEE.NONE' | translate }}</span>
</div>
<div class="mdl-cell mdl-cell--4-col" data-automation-id="header-due-date">
<span class="activiti-task-header__label">{{ 'TASK_DETAILS.LABELS.DUE' | translate }}: </span>
<span class="activiti-task-header__value" *ngIf="taskDetails?.dueDate">{{ taskDetails.dueDate }}</span>
<span class="activiti-task-header__value" *ngIf="!taskDetails?.dueDate">{{ 'TASK_DETAILS.DUE.NONE' |translate }}</span>
</div>
<div *ngIf="formName" class="mdl-cell mdl-cell--4-col" data-automation-id="header-form-name">
<span class="activiti-task-header__label">{{ 'TASK_DETAILS.LABELS.FORM' | translate }}: </span>
<span class="activiti-task-header__value" *ngIf="formName">{{ formName }}</span>
<span class="activiti-task-header__value" *ngIf="!formName">{{ 'TASK_DETAILS.FORM.NONE' | translate }}</span>
</div>
<div class="mdl-grid mdl-shadow--2dp">
<adf-card-view [properties]="properties"></adf-card-view>
<button *ngIf="!isAssignedToMe()" data-automation-id="header-claim-button" type="button" id="claim-task"
(click)="claimTask(taskDetails.id)" class="mdl-button">{{ 'TASK_DETAILS.BUTTON.CLAIM' | translate }}

View File

@@ -64,57 +64,64 @@ describe('ActivitiTaskHeader', () => {
window['componentHandler'] = componentHandler;
});
it('should render empty component if no form details provided', () => {
it('should render empty component if no task details provided', () => {
component.taskDetails = undefined;
fixture.detectChanges();
expect(fixture.debugElement.children.length).toBe(0);
});
it('should display assignee', () => {
component.ngOnChanges({});
fixture.detectChanges();
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .activiti-task-header__value'));
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-header__value'));
expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams');
});
it('should display placeholder if no assignee', () => {
component.taskDetails.assignee = null;
component.ngOnChanges({});
fixture.detectChanges();
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .activiti-task-header__value'));
expect(valueEl.nativeElement.innerText).toBe('TASK_DETAILS.ASSIGNEE.NONE');
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-header__value'));
expect(valueEl.nativeElement.innerText).toBe('No assignee');
});
it('should display the claim button if no assignee', () => {
component.taskDetails.assignee = null;
component.ngOnChanges({});
fixture.detectChanges();
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'));
expect(valueEl.nativeElement.innerText).toBe('TASK_DETAILS.BUTTON.CLAIM');
});
it('should display due date', () => {
component.taskDetails.dueDate = '2016-11-03T15:25:42.749+0000';
component.taskDetails.dueDate = '2016-11-03';
component.ngOnChanges({});
fixture.detectChanges();
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-due-date"] .activiti-task-header__value'));
expect(valueEl.nativeElement.innerText).toBe('2016-11-03T15:25:42.749+0000');
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-header__value'));
expect(valueEl.nativeElement.innerText).toBe('Nov 03 2016');
});
it('should display placeholder if no due date', () => {
component.taskDetails.dueDate = null;
component.ngOnChanges({});
fixture.detectChanges();
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-due-date"] .activiti-task-header__value'));
expect(valueEl.nativeElement.innerText).toBe('TASK_DETAILS.DUE.NONE');
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-header__value'));
expect(valueEl.nativeElement.innerText).toBe('No date');
});
it('should display form name', () => {
component.formName = 'test form';
component.ngOnChanges({});
fixture.detectChanges();
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-form-name"] .activiti-task-header__value'));
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-header__value'));
expect(valueEl.nativeElement.innerText).toBe('test form');
});
it('should not display form name if no form name provided', () => {
component.ngOnChanges({});
fixture.detectChanges();
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-form-name"] .activiti-task-header__value'));
expect(valueEl).toBeNull();
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-header__value'));
expect(valueEl.nativeElement.innerText).toBe('No form');
});
});

View File

@@ -15,9 +15,9 @@
* limitations under the License.
*/
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core';
import { TaskDetailsModel } from '../models/task-details.model';
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { AlfrescoTranslationService, LogService, CardViewModel } from 'ng2-alfresco-core';
import { TaskDetailsModel } from '../models/index';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
@Component({
@@ -25,7 +25,7 @@ import { ActivitiTaskListService } from './../services/activiti-tasklist.service
templateUrl: './activiti-task-header.component.html',
styleUrls: ['./activiti-task-header.component.css']
})
export class ActivitiTaskHeader {
export class ActivitiTaskHeader implements OnChanges {
@Input()
formName: string = null;
@@ -36,6 +36,8 @@ export class ActivitiTaskHeader {
@Output()
claim: EventEmitter<any> = new EventEmitter<any>();
properties: CardViewModel [];
constructor(private translateService: AlfrescoTranslationService,
private activitiTaskService: ActivitiTaskListService,
private logService: LogService) {
@@ -44,6 +46,31 @@ export class ActivitiTaskHeader {
}
}
ngOnChanges(changes: SimpleChanges) {
this.refreshData();
}
refreshData() {
if (this.taskDetails) {
this.properties = [
new CardViewModel({label: 'Status:', value: this.getTaskStatus(), key: 'status'}),
new CardViewModel({label: 'Due Date:', value: this.taskDetails.dueDate, format: 'MMM DD YYYY', key: 'dueDate', default: 'No date'}),
new CardViewModel({label: 'Category:', value: this.taskDetails.category, key: 'category', default: 'No category'}),
new CardViewModel(
{
label: 'Created By:',
value: this.taskDetails.getFullName(),
key: 'assignee',
default: 'No assignee'
}),
new CardViewModel({label: 'Created:', value: this.taskDetails.created, format: 'MMM DD YYYY', key: 'created'}),
new CardViewModel({label: 'Id:', value: this.taskDetails.id, key: 'id'}),
new CardViewModel({label: 'Description:', value: this.taskDetails.description, key: 'description', default: 'No description'}),
new CardViewModel({label: 'Form name:', value: this.formName, key: 'formName', default: 'No form'})
];
}
}
public hasAssignee(): boolean {
return (this.taskDetails && this.taskDetails.assignee) ? true : false;
}
@@ -52,6 +79,10 @@ export class ActivitiTaskHeader {
return this.taskDetails.assignee ? true : false;
}
getTaskStatus(): string {
return this.taskDetails.endDate ? 'Completed' : 'Running';
}
claimTask(taskId: string) {
this.activitiTaskService.claimTask(taskId).subscribe(
(res: any) => {

View File

@@ -90,4 +90,11 @@ export class TaskDetailsModel {
this.processInstanceStartUserId = obj && obj.processInstanceStartUserId || null;
this.taskDefinitionKey = obj && obj.taskDefinitionKey || null;
}
getFullName(): string {
if (this.assignee) {
return this.assignee.firstName + ' ' + this.assignee.lastName;
}
return '';
}
}

View File

@@ -52,18 +52,22 @@ import { DataColumnListComponent } from './src/components/data-column/data-colum
import { MATERIAL_DESIGN_DIRECTIVES } from './src/components/material/index';
import { CONTEXT_MENU_PROVIDERS, CONTEXT_MENU_DIRECTIVES } from './src/components/context-menu/index';
import { COLLAPSABLE_DIRECTIVES } from './src/components/collapsable/index';
import { VIEW_DIRECTIVES } from './src/components/view/index';
export * from './src/services/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/components/collapsable/index';
export * from './src/components/view/index';
export * from './src/directives/upload.directive';
export * from './src/utils/index';
export * from './src/events/base.event';
export * from './src/events/base-ui.event';
export * from './src/events/folder-created.event';
export * from './src/models/index';
export const ALFRESCO_CORE_PROVIDERS: any[] = [
NotificationService,
LogService,
@@ -107,6 +111,7 @@ export function createTranslateLoader(http: Http, logService: LogService) {
...MATERIAL_DESIGN_DIRECTIVES,
...CONTEXT_MENU_DIRECTIVES,
...COLLAPSABLE_DIRECTIVES,
...VIEW_DIRECTIVES,
UploadDirective,
DataColumnComponent,
DataColumnListComponent,
@@ -127,6 +132,7 @@ export function createTranslateLoader(http: Http, logService: LogService) {
...MATERIAL_DESIGN_DIRECTIVES,
...CONTEXT_MENU_DIRECTIVES,
...COLLAPSABLE_DIRECTIVES,
...VIEW_DIRECTIVES,
UploadDirective,
DataColumnComponent,
DataColumnListComponent,

View File

@@ -18,3 +18,4 @@
export * from './context-menu/index';
export * from './material/index';
export * from './collapsable/index';
export * from './view/index';

View File

@@ -0,0 +1,18 @@
:host {
width: 100%;
}
.adf-header__label {
font-weight: bold;
float: left;
width: 50%;
word-wrap: break-word;
}
.adf-header__value {
color: rgb(68, 138, 255);
}
.material-icons {
cursor: pointer;
}

View File

@@ -0,0 +1,6 @@
<div class="mdl-cell mdl-cell--12-col" *ngFor="let property of properties; let idx = index">
<div [attr.data-automation-id]="'header-' + property.key">
<div class="adf-header__label">{{ property.label }}</div>
<div class="adf-header__value">{{ getPropertyValue(property) }}</div>
</div>
</div>

View File

@@ -0,0 +1,99 @@
/*!
* @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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CardView } from './adf-card-view.component';
import { CardViewModel } from '../../models/card-view.model';
import { By } from '@angular/platform-browser';
describe('AdfCardView', () => {
let fixture: ComponentFixture<CardView>;
let component: CardView;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
CardView
],
providers: [
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CardView);
component = fixture.componentInstance;
});
it('should render the label and value', async(() => {
component.properties = [new CardViewModel({label: 'My label', value: 'My value'});
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let labelValue = fixture.debugElement.query(By.css('.adf-header__label'));
expect(labelValue).not.toBeNull();
expect(labelValue.nativeElement.innerText).toBe('My label');
let value = fixture.debugElement.query(By.css('.adf-header__value'));
expect(value).not.toBeNull();
expect(value.nativeElement.innerText).toBe('My value');
});
}));
it('should render the date in the correct format', async(() => {
component.properties = [new CardViewModel({
label: 'My date label', value: '2017-06-14',
format: 'MMM DD YYYY'
})];
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let labelValue = fixture.debugElement.query(By.css('.adf-header__label'));
expect(labelValue).not.toBeNull();
expect(labelValue.nativeElement.innerText).toBe('My date label');
let value = fixture.debugElement.query(By.css('.adf-header__value'));
expect(value).not.toBeNull();
expect(value.nativeElement.innerText).toBe('Jun 14 2017');
});
}));
it('should render the default value if the value is empty', async(() => {
component.properties = [new CardViewModel({
label: 'My default label',
default: 'default value'
})];
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let labelValue = fixture.debugElement.query(By.css('.adf-header__label'));
expect(labelValue).not.toBeNull();
expect(labelValue.nativeElement.innerText).toBe('My default label');
let value = fixture.debugElement.query(By.css('.adf-header__value'));
expect(value).not.toBeNull();
expect(value.nativeElement.innerText).toBe('default value');
});
}));
});

View File

@@ -0,0 +1,53 @@
/*!
* @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,
OnInit
} from '@angular/core';
import { CardViewModel } from '../../models/card-view.model';
import * as moment from 'moment';
@Component({
selector: 'adf-card-view',
templateUrl: './adf-card-view.component.html',
styleUrls: ['./adf-card-view.component.css']
})
export class CardView implements OnInit {
@Input()
properties: CardViewModel [];
constructor() {
}
ngOnInit() {
}
getPropertyValue(property: CardViewModel): string {
if (!property.value) {
return property.default;
} else if (property.format) {
return moment(property.value).format(property.format);
}
return property.value;
}
}

View File

@@ -0,0 +1,22 @@
/*!
* @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 { CardView } from './adf-card-view.component';
export const VIEW_DIRECTIVES: [any] = [
CardView
];

View File

@@ -0,0 +1,40 @@
/*!
* @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.
*/
/**
*
* This object represent the basic structure of a card view.
*
*
* @returns {CardViewModel} .
*/
export class CardViewModel {
label: string;
value: any;
key: any;
format: string;
default: string;
constructor(obj?: any) {
this.label = obj.label || '';
this.value = obj.value;
this.key = obj.key;
this.format = obj.format;
this.default = obj.default;
}
}

View File

@@ -0,0 +1,18 @@
/*!
* @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.
*/
export * from './card-view.model';