Improve the task list component

- Customize the task list column passing the json schema instead of DataTableAdapter
 - Add function to add people
 - Add function to add comments
 - Add function to add checklist
 - TaskFilter as a differenct component
 - Change the layout in the demo shell
 - Using Tabs instead of dropdown
This commit is contained in:
mauriziovitale84
2016-07-27 18:10:49 +01:00
parent d612cc8ffa
commit 8b96d5e8aa
24 changed files with 481 additions and 169 deletions

View File

@@ -3,9 +3,12 @@
font-weight: bold; font-weight: bold;
} }
.activiti {
background-color: #f5f5f5;
}
.task-column { .task-column {
background-color: #f5f5f5; background-color: #f5f5f5;
padding: 10px 10px 10px 10px; padding: 10px 10px 10px 10px;
border-left: solid 2px rgb(31,188,210); border: solid 2px rgb(31,188,210);
border-right : solid 2px rgb(31,188,210);
} }

View File

@@ -1,30 +1,60 @@
<div class="mdl-grid"> <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<div class="mdl-cell mdl-cell--2-col task-column" <header class="mdl-layout__header">
style=""> <!-- Tabs -->
<ul class="demo-list-item mdl-list"> <div class="mdl-layout__tab-bar mdl-js-ripple-effect">
<li class="mdl-list__item"> <a href="#scroll-tab-1" class="mdl-layout__tab is-active">TASK LIST</a>
<span class="mdl-list__item-primary-content"> <a href="#scroll-tab-2" class="mdl-layout__tab">PROCESS LIST</a>
<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="option-1"> <a href="#scroll-tab-3" class="mdl-layout__tab">REPORT</a>
<input type="radio" value="task-list"
(change)="setChoice($event)" name="options" id="option-1" checked class="mdl-radio__button">
<span class="mdl-radio__label">Task List</span>
</label>
</span>
</li>
<li class="mdl-list__item">
<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="option-2">
<input type="radio" value="process-list"
(change)="setChoice($event)" name="options" id="option-2" class="mdl-radio__button">
<span class="mdl-radio__label">Process List</span>
</label>
</li>
</ul>
</div> </div>
<div class="mdl-cell mdl-cell--3-col task-column" </header>
style=""> <main class="mdl-layout__content activiti">
<activiti-tasklist *ngIf="isTaskListSelected()" [data]="data" (rowClick)="onRowClick($event)"></activiti-tasklist> <section class="mdl-layout__tab-panel is-active" id="scroll-tab-1">
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column">
<span>Task Filters</span>
<activiti-filters (filterClick)="onFilterClick($event)"></activiti-filters>
</div>
<div class="mdl-cell mdl-cell--3-col task-column">
<span>Task List</span>
<activiti-tasklist *ngIf="isTaskListSelected()" [taskFilter]="taskFilter" [schemaColumn]="schemaColumn"
(rowClick)="onRowClick($event)" #activititasklist></activiti-tasklist>
</div> </div>
<div class="mdl-cell mdl-cell--7-col task-column"> <div class="mdl-cell mdl-cell--7-col task-column">
<activiti-task-details [taskId]="currentTaskId"></activiti-task-details> <span>Task Details</span>
<activiti-task-details [taskId]="currentTaskId" #activitidetails></activiti-task-details>
</div> </div>
</div> </div>
</div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-2">
<div class="page-content">
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column">
<span>Process Filters</span>
</div>
<div class="mdl-cell mdl-cell--3-col task-column">
<span>Process List</span>
</div>
<div class="mdl-cell mdl-cell--7-col task-column">
<span>Process Details</span>
</div>
</div>
</div>
</div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-3">
<div class="page-content"><!-- Your content goes here --></div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-4">
<div class="page-content"><!-- Your content goes here --></div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-5">
<div class="page-content"><!-- Your content goes here --></div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-6">
<div class="page-content"><!-- Your content goes here --></div>
</section>
</main>
</div>

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, OnInit, AfterViewChecked } from '@angular/core'; import { Component, OnInit, AfterViewChecked, ViewChild } from '@angular/core';
import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist'; import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist';
import { ActivitiForm } from 'ng2-activiti-form'; import { ActivitiForm } from 'ng2-activiti-form';
@@ -36,11 +36,19 @@ export class ActivitiDemoComponent implements OnInit, AfterViewChecked {
currentChoice: string = 'task-list'; currentChoice: string = 'task-list';
@ViewChild('activitidetails')
activitidetails: any;
@ViewChild('activititasklist')
activititasklist: any;
currentTaskId: string; currentTaskId: string;
data: ObjectDataTableAdapter; schemaColumn: any [] = [];
taskFilter: any;
constructor() { constructor() {
this.data = new ObjectDataTableAdapter([], []);
} }
setChoice($event) { setChoice($event) {
@@ -56,16 +64,20 @@ export class ActivitiDemoComponent implements OnInit, AfterViewChecked {
} }
ngOnInit() { ngOnInit() {
let schema = [ this.schemaColumn = [
{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', sortable: true}
]; ];
}
let columns = schema.map(col => new ObjectDataColumn(col)); onFilterClick(event: any) {
this.data.setColumns(columns); this.taskFilter = event;
this.activititasklist.load(this.taskFilter);
} }
onRowClick(taskId) { onRowClick(taskId) {
this.currentTaskId = taskId; this.currentTaskId = taskId;
this.activitidetails.loadDetails(this.currentTaskId);
} }
ngAfterViewChecked() { ngAfterViewChecked() {

View File

@@ -17,8 +17,8 @@
import { ActivitiTaskList } from './src/components/activiti-tasklist.component'; import { ActivitiTaskList } from './src/components/activiti-tasklist.component';
import { ActivitiTaskDetails } from './src/components/activiti-task-details.component'; import { ActivitiTaskDetails } from './src/components/activiti-task-details.component';
import { ActivitiFilters } from './src/components/activiti-filters.component';
export * from './src/components/activiti-tasklist.component'; export * from './src/components/activiti-tasklist.component';
export const ALFRESCO_TASKLIST_DIRECTIVES: [any] = [ActivitiTaskList, ActivitiTaskDetails]; export const ALFRESCO_TASKLIST_DIRECTIVES: [any] = [ActivitiFilters, ActivitiTaskList, ActivitiTaskDetails];

View File

@@ -3,7 +3,7 @@
} }
.activiti-label { .activiti-label {
color: rgb(255,152,0); font-weight: bolder;
} }
.material-icons:hover { .material-icons:hover {

View File

@@ -1,6 +1,6 @@
<span class="activiti-label mdl-badge" <span class="activiti-label mdl-badge"
[attr.data-badge]="checklist?.length">{{ 'TASK_DETAILS.LABELS.CHECKLIST' | translate }}</span> [attr.data-badge]="checklist?.length">{{ 'TASK_DETAILS.LABELS.CHECKLIST' | translate }}</span>
<div id="addChecklist" (click)="add()" class="icon material-icons">add</div> <div id="addChecklist" (click)="showDialog()" class="icon material-icons">add</div>
<div class="mdl-tooltip" for="addChecklist"> <div class="mdl-tooltip" for="addChecklist">
Add a checklist Add a checklist
</div> </div>
@@ -17,3 +17,17 @@
<div *ngIf="checklist?.length === 0"> <div *ngIf="checklist?.length === 0">
{{ 'TASK_DETAILS.CHECKLIST.NONE' | translate }} {{ 'TASK_DETAILS.CHECKLIST.NONE' | translate }}
</div> </div>
<dialog class="mdl-dialog" #dialog>
<h4 class="mdl-dialog__title">New Task</h4>
<div class="mdl-dialog__content">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" [(ngModel)]="taskName" id="task" />
<label class="mdl-textfield__label" for="task">Name</label>
</div>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="add()" class="mdl-button">Add Checklist</button>
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
</div>
</dialog>

View File

@@ -15,10 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnInit, OnChanges } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { TaskDetailsModel } from '../models/task-details.model'; import { TaskDetailsModel } from '../models/task-details.model';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
declare let componentHandler: any; declare let componentHandler: any;
declare let __moduleName: string; declare let __moduleName: string;
@@ -32,13 +34,21 @@ declare let __moduleName: string;
pipes: [AlfrescoPipeTranslate] pipes: [AlfrescoPipeTranslate]
}) })
export class ActivitiChecklist implements OnInit, OnChanges { export class ActivitiChecklist implements OnInit {
@Input() @Input()
taskId: string; taskId: string;
@ViewChild('dialog')
dialog: any;
taskName: string;
checklist: TaskDetailsModel [] = []; checklist: TaskDetailsModel [] = [];
private taskObserver: Observer<TaskDetailsModel>;
task$: Observable<TaskDetailsModel>;
/** /**
* Constructor * Constructor
* @param auth * @param auth
@@ -51,31 +61,60 @@ export class ActivitiChecklist implements OnInit, OnChanges {
if (translate) { if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist'); translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
} }
this.task$ = new Observable<TaskDetailsModel>(observer => this.taskObserver = observer).share();
} }
ngOnInit() { ngOnInit() {
this.task$.subscribe((task: TaskDetailsModel) => {
this.checklist.push(task);
});
if (this.taskId) { if (this.taskId) {
this.load(this.taskId); this.load(this.taskId);
} }
} }
ngOnChanges(change) {
this.load(this.taskId);
}
public add() {
alert('Add CheckList');
}
public load(taskId: string) { public load(taskId: string) {
this.checklist = [];
if (this.taskId) { if (this.taskId) {
this.activitiTaskList.getTaskChecklist(this.taskId).subscribe( this.activitiTaskList.getTaskChecklist(this.taskId).subscribe(
(res: TaskDetailsModel[]) => { (res: TaskDetailsModel[]) => {
this.checklist = res; res.forEach((task) => {
this.taskObserver.next(task);
});
},
(err) => {
console.log(err);
} }
); );
} else { } else {
this.checklist = []; this.checklist = [];
} }
} }
public showDialog() {
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
public add() {
let newTask = new TaskDetailsModel({name: this.taskName, parentTaskId: this.taskId, assignee: {id: '1'}});
this.activitiTaskList.addTask(newTask).subscribe(
(res: TaskDetailsModel) => {
this.checklist.push(res);
},
(err) => {
console.log(err);
}
);
this.cancel();
}
public cancel() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
}
} }

View File

@@ -3,7 +3,7 @@
} }
.activiti-label { .activiti-label {
color: rgb(255,152,0); font-weight: bolder;
} }
.material-icons:hover { .material-icons:hover {

View File

@@ -20,12 +20,12 @@
</div> </div>
<dialog class="mdl-dialog" style="width: 35%;" #dialog> <dialog class="mdl-dialog" #dialog>
<h4 class="mdl-dialog__title">New comment</h4> <h4 class="mdl-dialog__title">New comment</h4>
<div class="mdl-dialog__content"> <div class="mdl-dialog__content">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"> <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<textarea class="mdl-textfield__input" type="text" rows="1" id="commentText"></textarea> <textarea class="mdl-textfield__input" type="text" [(ngModel)]="message" rows="1" id="commentText"></textarea>
<label class="mdl-textfield__label" for="commentText">Text</label> <label class="mdl-textfield__label" for="commentText">Message</label>
</div> </div>
</div> </div>
<div class="mdl-dialog__actions"> <div class="mdl-dialog__actions">

View File

@@ -15,10 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnInit, OnChanges, ViewChild } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { Comment } from '../models/comment.model'; import { Comment } from '../models/comment.model';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
declare let componentHandler: any; declare let componentHandler: any;
declare let __moduleName: string; declare let __moduleName: string;
@@ -32,7 +34,7 @@ declare let __moduleName: string;
pipes: [ AlfrescoPipeTranslate ] pipes: [ AlfrescoPipeTranslate ]
}) })
export class ActivitiComments implements OnInit, OnChanges { export class ActivitiComments implements OnInit {
@Input() @Input()
taskId: string; taskId: string;
@@ -42,6 +44,11 @@ export class ActivitiComments implements OnInit, OnChanges {
comments: Comment [] = []; comments: Comment [] = [];
private commentObserver: Observer<Comment>;
comment$: Observable<Comment>;
message: string;
/** /**
* Constructor * Constructor
* @param auth * @param auth
@@ -54,23 +61,32 @@ export class ActivitiComments implements OnInit, OnChanges {
if (translate) { if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist'); translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
} }
this.comment$ = new Observable<Comment>(observer => this.commentObserver = observer).share();
} }
ngOnInit() { ngOnInit() {
this.comment$.subscribe((comment: Comment) => {
this.comments.push(comment);
});
if (this.taskId) { if (this.taskId) {
this.load(this.taskId); this.load(this.taskId);
} }
} }
ngOnChanges(change) {
this.load(this.taskId);
}
public load(taskId: string) { public load(taskId: string) {
this.comments = [];
if (this.taskId) { if (this.taskId) {
this.activitiTaskList.getTaskComments(this.taskId).subscribe( this.activitiTaskList.getTaskComments(this.taskId).subscribe(
(res: Comment[]) => { (res: Comment[]) => {
this.comments = res; res.forEach((comment) => {
this.commentObserver.next(comment);
});
},
(err) => {
console.log(err);
} }
); );
} else { } else {
@@ -85,14 +101,15 @@ export class ActivitiComments implements OnInit, OnChanges {
} }
public add() { public add() {
alert('add comment'); this.activitiTaskList.addTaskComment(this.taskId, this.message).subscribe(
if (this.taskId) { (res: Comment) => {
this.activitiTaskList.addTaskComment(this.taskId, 'test comment').subscribe( this.comments.push(res);
(res: Comment[]) => { this.message = '';
this.comments = res; },
(err) => {
console.log(err);
} }
); );
}
this.cancel(); this.cancel();
} }

View File

@@ -0,0 +1,10 @@
<div class="menu-container">
<ul class='mdl-list'>
<li class="mdl-list__item"(click)="selectFilter(filter)" *ngFor="let filter of filters">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">assignment</i>
{{filter.name}}
</span>
</li>
</ul>
</div>

View File

@@ -0,0 +1,94 @@
/*!
* @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, Output, EventEmitter, OnInit} from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { FilterModel } from '../models/filter.model';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
declare let componentHandler: any;
declare let __moduleName: string;
@Component({
selector: 'activiti-filters',
moduleId: __moduleName,
templateUrl: './activiti-filters.component.html',
providers: [ActivitiTaskListService],
pipes: [ AlfrescoPipeTranslate ]
})
export class ActivitiFilters implements OnInit {
@Output()
filterClick: EventEmitter<FilterModel> = new EventEmitter<FilterModel>();
private filterObserver: Observer<FilterModel>;
filter$: Observable<FilterModel>;
currentFilter: FilterModel;
filters: FilterModel [] = [];
/**
* Constructor
* @param auth
* @param translate
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
public activiti: ActivitiTaskListService) {
this.filter$ = new Observable<FilterModel>(observer => this.filterObserver = observer).share();
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
}
}
ngOnInit() {
this.filter$.subscribe((filter: FilterModel) => {
this.filters.push(filter);
});
this.load();
}
/**
* The method call the adapter data table component for render the task list
* @param tasks
*/
private load() {
this.activiti.getTaskListFilters().subscribe(
(res: FilterModel[]) => {
res.forEach((filter) => {
this.filterObserver.next(filter);
});
},
(err) => {
console.log(err);
}
);
}
/**
* Pass the selected filter as next
* @param filter
*/
public selectFilter(filter: FilterModel) {
this.filterClick.emit(filter);
}
}

View File

@@ -3,7 +3,7 @@
} }
.activiti-label { .activiti-label {
color: rgb(255,152,0); font-weight: bolder;
} }
.material-icons:hover { .material-icons:hover {

View File

@@ -1,6 +1,6 @@
<span class="activiti-label mdl-badge" <span class="activiti-label mdl-badge"
[attr.data-badge]="people?.length">{{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }}</span> [attr.data-badge]="people?.length">{{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }}</span>
<div id="addPeople" class="icon material-icons">add</div> <div id="addPeople" (click)="showDialog()" class="icon material-icons">add</div>
<div class="mdl-tooltip" for="addPeople"> <div class="mdl-tooltip" for="addPeople">
Add a people Add a people
</div> </div>
@@ -17,3 +17,17 @@
<div *ngIf="people?.length === 0"> <div *ngIf="people?.length === 0">
{{ 'TASK_DETAILS.PEOPLE.NONE' | translate }} {{ 'TASK_DETAILS.PEOPLE.NONE' | translate }}
</div> </div>
<dialog class="mdl-dialog" #dialog>
<h4 class="mdl-dialog__title">New User</h4>
<div class="mdl-dialog__content">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" id="people" />
<label class="mdl-textfield__label" for="people">Name</label>
</div>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="add()" class="mdl-button">Add User</button>
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
</div>
</dialog>

View File

@@ -15,9 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { User } from '../models/user.model'; import { User } from '../models/user.model';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
declare let componentHandler: any; declare let componentHandler: any;
declare let __moduleName: string; declare let __moduleName: string;
@@ -35,6 +37,12 @@ export class ActivitiPeople implements OnInit {
@Input() @Input()
people: User [] = []; people: User [] = [];
@ViewChild('dialog')
dialog: any;
private peopleObserver: Observer<User>;
people$: Observable<User>;
/** /**
* Constructor * Constructor
* @param auth * @param auth
@@ -46,10 +54,31 @@ export class ActivitiPeople implements OnInit {
if (translate) { if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist'); translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
} }
this.people$ = new Observable<User>(observer => this.peopleObserver = observer).share();
} }
ngOnInit() { ngOnInit() {
this.people$.subscribe((user: User) => {
this.people.push(user);
});
}
public showDialog() {
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
public add() {
alert('add people');
this.cancel();
}
public cancel() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
} }
} }

View File

@@ -1,19 +1,17 @@
<div *ngIf="!taskDetails"> <div *ngIf="!taskDetails">{{ 'TASK_DETAILS.MESSAGES.NONE' | translate }}</div>
<h3 style="text-align: center">{{ 'TASK_DETAILS.MESSAGES.NONE' | translate }}</h3>
</div>
<div *ngIf="taskDetails"> <div *ngIf="taskDetails">
<h2 class="mdl-card__title-text">{{taskDetails.name}}</h2> <h2 class="mdl-card__title-text">{{taskDetails.name}}</h2>
<activiti-task-header [taskDetails]="taskDetails"></activiti-task-header> <activiti-task-header [taskDetails]="taskDetails" #activitiheader></activiti-task-header>
<div class="mdl-grid"> <div class="mdl-grid">
<div class="mdl-cell mdl-cell--4-col"> <div class="mdl-cell mdl-cell--4-col">
<activiti-people [people]="taskPeople"></activiti-people> <activiti-people [people]="taskPeople"></activiti-people>
</div> </div>
<div class="mdl-cell mdl-cell--4-col"> <div class="mdl-cell mdl-cell--4-col">
<activiti-comments [taskId]="taskId"></activiti-comments> <activiti-comments [taskId]="taskId" #activiticomments></activiti-comments>
</div> </div>
<div class="mdl-cell mdl-cell--4-col"> <div class="mdl-cell mdl-cell--4-col">
<activiti-checklist [taskId]="taskId"></activiti-checklist> <activiti-checklist [taskId]="taskId" #activitichecklist></activiti-checklist>
</div> </div>
</div> </div>
<activiti-form *ngIf="taskDetails.formKey" [taskId]="taskDetails.id" #activitiForm ></activiti-form> <activiti-form *ngIf="taskDetails.formKey" [taskId]="taskId" #activitiForm ></activiti-form>
</div> </div>

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnInit, OnChanges } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { ActivitiTaskHeader } from './activiti-task-header.component'; import { ActivitiTaskHeader } from './activiti-task-header.component';
@@ -40,11 +40,17 @@ declare let __moduleName: string;
pipes: [AlfrescoPipeTranslate] pipes: [AlfrescoPipeTranslate]
}) })
export class ActivitiTaskDetails implements OnInit, OnChanges { export class ActivitiTaskDetails implements OnInit {
@Input() @Input()
taskId: string; taskId: string;
@ViewChild('activiticomments')
activiticomments: any;
@ViewChild('activitichecklist')
activitichecklist: any;
taskDetails: TaskDetailsModel; taskDetails: TaskDetailsModel;
taskForm: FormModel; taskForm: FormModel;
@@ -68,30 +74,29 @@ export class ActivitiTaskDetails implements OnInit, OnChanges {
ngOnInit() { ngOnInit() {
if (this.taskId) { if (this.taskId) {
this.activitiTaskList.getTaskDetails(this.taskId).subscribe(
(res: TaskDetailsModel) => {
this.taskDetails = res;
console.log(this.taskDetails);
}
);
}
}
ngOnChanges(change) {
this.loadDetails(this.taskId); this.loadDetails(this.taskId);
} }
}
loadDetails(id: string) { loadDetails(taskId: string) {
this.taskForm = null; this.taskForm = null;
this.taskPeople = []; this.taskPeople = [];
if (id) { if (taskId) {
this.activitiTaskList.getTaskDetails(id).subscribe( this.activitiTaskList.getTaskDetails(taskId).subscribe(
(res: TaskDetailsModel) => { (res: TaskDetailsModel) => {
this.taskDetails = res; this.taskDetails = res;
if (this.taskDetails && this.taskDetails.involvedPeople) { if (this.taskDetails && this.taskDetails.involvedPeople) {
this.taskDetails.involvedPeople.forEach((user) => { this.taskDetails.involvedPeople.forEach((user) => {
this.taskPeople.push(new User(user.id, user.email, user.firstName, user.lastName)); this.taskPeople.push(new User(user.id, user.email, user.firstName, user.lastName));
}); });
if (this.activiticomments) {
this.activiticomments.load(this.taskDetails.id);
}
if (this.activitichecklist) {
this.activitichecklist.load(this.taskDetails.id);
}
} }
console.log(this.taskDetails); console.log(this.taskDetails);
} }

View File

@@ -3,5 +3,5 @@
} }
.activiti-label { .activiti-label {
color: rgb(255,152,0); font-weight: bolder;
} }

View File

@@ -1,15 +1,5 @@
<div class="menu-container"> <div *ngIf="!taskFilter">{{ 'TASK_FILTERS.MESSAGES.NONE' | translate }}</div>
<ul class='mdl-list'> <div *ngIf="taskFilter">
<li class="mdl-list__item"(click)="selectFilter(filter)" *ngFor="let filter of filtersList | async">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">assignment</i>
{{filter.name}}
</span>
</li>
</ul>
</div>
<div *ngIf="currentFilter">
<div *ngIf="!isTaskListEmpty()"> <div *ngIf="!isTaskListEmpty()">
<alfresco-datatable <alfresco-datatable
[data]="tasks" [data]="tasks"
@@ -20,4 +10,3 @@
{{ 'TASK_LIST.MESSAGES.NONE' | translate }} {{ 'TASK_LIST.MESSAGES.NONE' | translate }}
</div> </div>
</div> </div>
<div *ngIf="!currentFilter">{{ 'TASK_FILTERS.MESSAGES.NONE' | translate }}</div>

View File

@@ -32,6 +32,7 @@ describe('ActivitiTaskList', () => {
let taskList: ActivitiTaskList; let taskList: ActivitiTaskList;
/*
let fakeGlobalFilter = { let fakeGlobalFilter = {
size: 2, total: 2, start: 0, size: 2, total: 2, start: 0,
data: [ data: [
@@ -45,6 +46,7 @@ describe('ActivitiTaskList', () => {
} }
] ]
}; };
*/
let fakeGlobalTask = { let fakeGlobalTask = {
size: 1, total: 12, start: 0, size: 1, total: 12, start: 0,
@@ -64,27 +66,49 @@ describe('ActivitiTaskList', () => {
] ]
}; };
/*
let fakeErrorTaskList = { let fakeErrorTaskList = {
error: 'wrong request' error: 'wrong request'
}; };
*/
let fakeGlobalFilterPromise = new Promise(function (resolve, reject) {
resolve(fakeGlobalFilter);
});
let fakeGlobalTaskPromise = new Promise(function (resolve, reject) { let fakeGlobalTaskPromise = new Promise(function (resolve, reject) {
resolve(fakeGlobalTask); resolve(fakeGlobalTask);
}); });
/*
let fakeGlobalFilterPromise = new Promise(function (resolve, reject) {
resolve(fakeGlobalFilter);
});
let fakeErrorTaskPromise = new Promise(function (resolve, reject) { let fakeErrorTaskPromise = new Promise(function (resolve, reject) {
reject(fakeErrorTaskList); reject(fakeErrorTaskList);
}); });*/
beforeEach(() => { beforeEach(() => {
let activitiSerevice = new ActivitiTaskListService(null, null); let activitiSerevice = new ActivitiTaskListService(null, null);
taskList = new ActivitiTaskList(null, null, activitiSerevice); taskList = new ActivitiTaskList(null, null, activitiSerevice);
}); });
it('should return the task list when the taskFilter is passed', (done) => {
spyOn(taskList.activiti, 'getTasks').and.returnValue(Observable.fromPromise(fakeGlobalTaskPromise));
// spyOn(taskList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
taskList.taskFilter = new FilterModel('name', false, 'icon', '', 'open', 'fake-assignee');
taskList.onSuccess.subscribe( () => {
expect(taskList.tasks).toBeDefined();
expect(taskList.tasks.getRows().length).toEqual(2);
expect(taskList.tasks.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...');
expect(taskList.tasks.getRows()[1].getValue('name')).toEqual('Nameless task');
done();
});
taskList.ngOnInit();
});
/*
it('should return the default filters', (done) => { it('should return the default filters', (done) => {
spyOn(taskList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise)); spyOn(taskList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
taskList.ngOnInit(); taskList.ngOnInit();
@@ -156,5 +180,6 @@ describe('ActivitiTaskList', () => {
done(); done();
}); });
}); });
*/
}); });

View File

@@ -20,8 +20,6 @@ import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipe
import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter, DataTableAdapter, DataRowEvent } from 'ng2-alfresco-datatable'; import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter, DataTableAdapter, DataRowEvent } from 'ng2-alfresco-datatable';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { FilterModel } from '../models/filter.model'; import { FilterModel } from '../models/filter.model';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
declare let componentHandler: any; declare let componentHandler: any;
declare let __moduleName: string; declare let __moduleName: string;
@@ -38,20 +36,28 @@ declare let __moduleName: string;
export class ActivitiTaskList implements OnInit { export class ActivitiTaskList implements OnInit {
@Input() @Input()
data: DataTableAdapter; taskFilter: FilterModel;
@Input()
schemaColumn: any[] = [
{type: 'text', key: 'id', title: 'Id'},
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true},
{type: 'text', key: 'formKey', title: 'Form Key', sortable: true},
{type: 'text', key: 'created', title: 'Created', sortable: true}
];
@Output() @Output()
rowClick: EventEmitter<string> = new EventEmitter<string>(); rowClick: EventEmitter<string> = new EventEmitter<string>();
private filterObserver: Observer<FilterModel>; @Output()
onSuccess: EventEmitter<string> = new EventEmitter<string>();
filter$: Observable<FilterModel>; data: DataTableAdapter;
tasks: ObjectDataTableAdapter; tasks: ObjectDataTableAdapter;
currentFilter: FilterModel;
currentTaskId: string; currentTaskId: string;
filtersList: Observable<FilterModel>;
/** /**
* Constructor * Constructor
* @param auth * @param auth
@@ -60,7 +66,6 @@ export class ActivitiTaskList implements OnInit {
constructor(private auth: AlfrescoAuthenticationService, constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService, private translate: AlfrescoTranslationService,
public activiti: ActivitiTaskListService) { public activiti: ActivitiTaskListService) {
this.filter$ = new Observable<FilterModel>(observer => this.filterObserver = observer).share();
if (translate) { if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist'); translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
@@ -68,40 +73,47 @@ export class ActivitiTaskList implements OnInit {
} }
ngOnInit() { ngOnInit() {
this.filtersList = this.activiti.getTaskListFilters().map(res => (res.data)); this.data = new ObjectDataTableAdapter(
[],
this.schemaColumn
);
this.filter$.subscribe((filter: FilterModel) => { if (this.taskFilter) {
this.load(this.taskFilter);
}
}
public load(filter: FilterModel) {
this.activiti.getTasks(filter).subscribe( this.activiti.getTasks(filter).subscribe(
(res) => { (res) => {
this.loadTasks(res.data); this.renderTasks(res.data);
this.onSuccess.emit('Task List loaded');
}, (err) => { }, (err) => {
console.error(err); console.error(err);
}); });
});
} }
/** /**
* The method call the adapter data table component for render the task list * The method call the adapter data table component for render the task list
* @param tasks * @param tasks
*/ */
private loadTasks(tasks: any[]) { private renderTasks(tasks: any[]) {
tasks = this.optimizeTaskName(tasks); tasks = this.optimizeTaskName(tasks);
this.tasks = new ObjectDataTableAdapter(tasks, this.data.getColumns()); this.tasks = new ObjectDataTableAdapter(tasks, this.data.getColumns());
} }
/** /**
* Pass the selected filter as next * Check if the tasks list is empty
* @param filter * @returns {ObjectDataTableAdapter|boolean}
*/ */
public selectFilter(filter: FilterModel) {
this.currentFilter = filter;
this.filterObserver.next(filter);
}
isTaskListEmpty(): boolean { isTaskListEmpty(): boolean {
return this.tasks && this.tasks.getRows().length === 0; return this.tasks && this.tasks.getRows().length === 0;
} }
/**
* Emit the event rowClick passing the current task id when the row is clicked
* @param event
*/
onRowClick(event: DataRowEvent) { onRowClick(event: DataRowEvent) {
let item = event; let item = event;
this.currentTaskId = item.value.getValue('id'); this.currentTaskId = item.value.getValue('id');

View File

@@ -29,11 +29,11 @@ export class FilterModel {
icon: string; icon: string;
filter: FilterParamsModel; filter: FilterParamsModel;
constructor(name: string, recent: boolean, icon: string, state: string, assignment: string) { constructor(name: string, recent: boolean, icon: string, query: string, state: string, assignment: string) {
this.name = name; this.name = name;
this.recent = recent; this.recent = recent;
this.icon = icon; this.icon = icon;
this.filter = new FilterParamsModel(name, state, assignment); this.filter = new FilterParamsModel(query, state, assignment);
} }
} }
@@ -49,8 +49,8 @@ export class FilterParamsModel {
state: string; state: string;
assignment: string; assignment: string;
constructor(name: string, state: string, assignment: string) { constructor(query: string, state: string, assignment: string) {
this.name = name; this.name = query;
this.state = state; this.state = state;
this.assignment = assignment; this.assignment = assignment;
} }

View File

@@ -23,7 +23,7 @@ import { HTTP_PROVIDERS } from '@angular/http';
declare let AlfrescoApi: any; declare let AlfrescoApi: any;
declare let jasmine: any; declare let jasmine: any;
describe('AlfrescoUploadService', () => { describe('ActivitiTaskListService', () => {
let service, options: any; let service, options: any;
options = { options = {
@@ -72,11 +72,9 @@ describe('AlfrescoUploadService', () => {
let filters = service.getTaskListFilters(); let filters = service.getTaskListFilters();
filters.subscribe(res => { filters.subscribe(res => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.size).toEqual(2); expect(res.length).toEqual(2);
expect(res.total).toEqual(2); expect(res[0].name).toEqual('FakeInvolvedTasks');
expect(res.data.length).toEqual(2); expect(res[1].name).toEqual('FakeMyTasks');
expect(res.data[0].name).toEqual('FakeInvolvedTasks');
expect(res.data[1].name).toEqual('FakeMyTasks');
done(); done();
}); });

View File

@@ -37,8 +37,15 @@ export class ActivitiTaskListService {
*/ */
getTaskListFilters(): Observable<any> { getTaskListFilters(): Observable<any> {
return Observable.fromPromise(this.callApiTaskFilters()) return Observable.fromPromise(this.callApiTaskFilters())
.map((res: Response) => { .map(res => res.json())
return res.json(); .map((response: any) => {
let filters: FilterModel[] = [];
response.data.forEach((filter) => {
let filterModel = new FilterModel(filter.name, filter.recent, filter.icon,
filter.filter.name, filter.filter.state, filter.filter.assignment);
filters.push(filterModel);
});
return filters;
}) })
.catch(this.handleError); .catch(this.handleError);
} }
@@ -52,7 +59,7 @@ export class ActivitiTaskListService {
// data.filterId = filter.id; // data.filterId = filter.id;
// data.filter = filter.filter; // data.filter = filter.filter;
data = filter.filter; data = filter.filter;
data.text = filter.filter.name; // data.text = filter.filter.name;
data = JSON.stringify(data); data = JSON.stringify(data);
@@ -87,17 +94,20 @@ export class ActivitiTaskListService {
.catch(this.handleError); .catch(this.handleError);
} }
addTaskComment(id: string, message: string): Observable<Comment[]> { addTaskComment(id: string, message: string): Observable<Comment> {
return Observable.fromPromise(this.callApiAddTaskComment(id, message)) return Observable.fromPromise(this.callApiAddTaskComment(id, message))
.map(res => res.json()) .map(res => res.json())
.map((response: any) => { .map((response: Comment) => {
let comments: Comment[] = []; return new Comment(response.id, response.message, response.created, response.createdBy);
response.data.forEach((comment) => { })
let user = new User( .catch(this.handleError);
comment.createdBy.id, comment.createdBy.email, comment.createdBy.firstName, comment.createdBy.lastName); }
comments.push(new Comment(comment.id, comment.message, comment.created, user));
}); addTask(task: TaskDetailsModel): Observable<TaskDetailsModel> {
return comments; return Observable.fromPromise(this.callApiAddTask(task))
.map(res => res.json())
.map((response: TaskDetailsModel) => {
return new TaskDetailsModel(response);
}) })
.catch(this.handleError); .catch(this.handleError);
} }
@@ -185,6 +195,19 @@ export class ActivitiTaskListService {
.post(url, body, options).toPromise(); .post(url, body, options).toPromise();
} }
private callApiAddTask(task: TaskDetailsModel) {
let url = `${this.basePath}/api/enterprise/tasks/${task.parentTaskId}/checklist`;
let headers = new Headers({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
});
let body = JSON.stringify(task);
let options = new RequestOptions({headers: headers});
return this.http
.post(url, body, options).toPromise();
}
private callApiTaskChecklist(id: string) { private callApiTaskChecklist(id: string) {
let url = `${this.basePath}/api/enterprise/tasks/${id}/checklist`; let url = `${this.basePath}/api/enterprise/tasks/${id}/checklist`;
let headers = new Headers({ let headers = new Headers({