Basic single page with activiti task list and form render

This commit is contained in:
mauriziovitale84
2016-07-26 10:36:19 +01:00
parent 0d5274f54c
commit b96dcb7bb0
14 changed files with 416 additions and 110 deletions

View File

@@ -1,85 +0,0 @@
/*!
* @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 { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, Response } from '@angular/http';
// import { Observable } from 'rxjs/Rx';
@Injectable()
export class ActivitiService {
constructor(private http: Http) {}
login(username: string, password: string) {
let url = 'http://localhost:9999/activiti-app/app/authentication';
let headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({ headers: headers });
let data = 'j_username='
+ encodeURIComponent(username)
+ '&j_password='
+ encodeURIComponent(password)
+ '&_spring_security_remember_me=true&submit=Login';
return this.http
.post(url, data, options)
.toPromise()
// .then(res => console.log(res))
.catch(this.handleError);
}
getTasks() {
// emulate filter value
let data = JSON.stringify({
'page': 0,
'filterId': 3,
'filter': {
'sort': 'created-desc',
'name': '',
'state': 'open',
'assignment': 'involved'
},
'appDefinitionId': null
});
let url = 'http://localhost:9999/activiti-app/app/rest/filter/tasks';
let headers = new Headers({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({ headers: headers });
return this.http
.post(url, data, options)
.toPromise()
.then(this.parseJSON)
.catch(this.handleError);
}
private parseJSON(res: Response) {
let body = res.json();
return body.data || { };
}
private handleError(error: any) {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}

View File

@@ -0,0 +1,24 @@
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col">
<ul class="demo-list-item mdl-list">
<li class="mdl-list__item">
<span class="mdl-list__item-primary-content">
<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="option-1">
<input type="radio" id="option-1" class="mdl-radio__button" name="options" value="1" checked>
<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" id="option-2" class="mdl-radio__button" name="options" value="2">
<span class="mdl-radio__label">Process List</span>
</label>
</li>
</ul>
</div>
<div class="mdl-cell mdl-cell--2-col">
<activiti-tasklist [data]="data"></activiti-tasklist>
</div>
<div class="mdl-cell mdl-cell--8-col">Task Details</div>
</div>

View File

@@ -16,36 +16,84 @@
*/ */
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivitiTaskList } from 'ng2-activiti-tasklist'; import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist';
import { ActivitiForm } from 'ng2-activiti-form';
import { ObjectDataTableAdapter, ObjectDataColumn } from 'ng2-alfresco-datatable'; import { ObjectDataTableAdapter, ObjectDataColumn } from 'ng2-alfresco-datatable';
@Component({ @Component({
selector: 'tasks-demo', selector: 'tasks-demo',
template: ` template: `
<div class="container"> <div class="mdl-grid">
<activiti-tasklist [data]="data"></activiti-tasklist> <div class="mdl-cell mdl-cell--2-col"
style="background-color: #ececec; padding: 10px 10px 10px 10px; border-left: solid 2px rgb(31,188,210); border-right : solid 2px rgb(31,188,210);">
<ul class="demo-list-item mdl-list">
<li class="mdl-list__item">
<span class="mdl-list__item-primary-content">
<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="option-1">
<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"
style="background-color: #ececec; padding: 10px 10px 10px 10px; border-left: solid 2px rgb(31,188,210); border-right : solid 2px rgb(31,188,210);">
<activiti-tasklist *ngIf="isTaskListSelected()" [data]="data" (rowClick)="onRowClick($event)"></activiti-tasklist>
</div>
<div class="mdl-cell mdl-cell--7-col"
style="background-color: #ececec; padding: 10px 10px 10px 10px; border-left: solid 2px rgb(31,188,210); border-right : solid 2px rgb(31,188,210);">
<activiti-task-details [taskId]="currentTaskId"></activiti-task-details>
</div>
</div>
`, `,
directives: [ActivitiTaskList], directives: [ALFRESCO_TASKLIST_DIRECTIVES, ActivitiForm],
styles: [':host > .container { padding: 10px; }'] styles: [':host > .container { padding: 10px; }']
}) })
export class TasksDemoComponent implements OnInit { export class TasksDemoComponent implements OnInit {
currentChoice: string = 'task-list';
currentTaskId: string;
data: ObjectDataTableAdapter; data: ObjectDataTableAdapter;
constructor() { constructor() {
this.data = new ObjectDataTableAdapter([], []); this.data = new ObjectDataTableAdapter([], []);
} }
setChoice($event) {
this.currentChoice = $event.target.value;
}
isProcessListSelected() {
return this.currentChoice === 'process-list';
}
isTaskListSelected() {
return this.currentChoice === 'task-list';
}
ngOnInit() { ngOnInit() {
let schema = [ let schema = [
{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: 'formKey', title: 'Form Key', sortable: true},
{type: 'text', key: 'created', title: 'Created', sortable: true}
]; ];
let columns = schema.map(col => new ObjectDataColumn(col)); let columns = schema.map(col => new ObjectDataColumn(col));
this.data.setColumns(columns); this.data.setColumns(columns);
} }
onRowClick(taskId) {
this.currentTaskId = taskId;
}
} }

View File

@@ -0,0 +1,17 @@
{
"TASK_LIST": {
"MESSAGES": {
"NONE": "No tasks list found."
}
},
"TASK_DETAILS": {
"MESSAGES": {
"NONE": "No task details found."
}
},
"TASK_FILTERS": {
"MESSAGES": {
"NONE": "No task filter selected."
}
}
}

View File

@@ -0,0 +1,17 @@
{
"TASK_LIST": {
"MESSAGES": {
"NONE": "Nessuna lista tasks trovata."
}
},
"TASK_DETAILS": {
"MESSAGES": {
"NONE": "Nessun dettaglio task trovato."
}
},
"TASK_FILTERS": {
"MESSAGES": {
"NONE": "Nessun filtro task selezionato."
}
}
}

View File

@@ -16,7 +16,9 @@
*/ */
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';
export * from './src/components/activiti-tasklist.component'; export * from './src/components/activiti-tasklist.component';
export const ALFRESCO_TASKLIST_DIRECTIVES: [any] = [ActivitiTaskList]; export const ALFRESCO_TASKLIST_DIRECTIVES: [any] = [ActivitiTaskList, ActivitiTaskDetails];

View File

@@ -71,6 +71,7 @@
"ng2-translate": "2.2.2", "ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0", "ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0", "ng2-alfresco-datatable": "0.2.0",
"ng2-activiti-form": "0.2.0",
"alfresco-js-api": "^0.1.0" "alfresco-js-api": "^0.1.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -0,0 +1,3 @@
:host {
width: 100%;
}

View File

@@ -0,0 +1,13 @@
<div *ngIf="!taskDetails">
<h3 style="text-align: center">{{ 'TASK_DETAILS.MESSAGES.NONE' | translate }}</h3>
</div>
<div *ngIf="taskDetails">
<h2 class="mdl-card__title-text">{{taskDetails.name}}</h2>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--4-col">Email: {{taskDetails.assignee.email}}</div>
<div class="mdl-cell mdl-cell--4-col">FirstName: {{taskDetails.assignee.firstName}}</div>
<div class="mdl-cell mdl-cell--4-col">LastName: {{taskDetails.assignee.lastName}}</div>
</div>
<activiti-form *ngIf="taskDetails.formKey" [taskId]="taskDetails.id" ></activiti-form>
</div>

View File

@@ -0,0 +1,92 @@
/*!
* @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, OnChanges } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { TaskDetailsModel } from '../models/task-details.model';
import { ActivitiForm } from 'ng2-activiti-form';
declare let componentHandler: any;
declare let __moduleName: string;
@Component({
selector: 'activiti-task-details',
moduleId: __moduleName,
templateUrl: './activiti-task-details.component.html',
styleUrls: ['./activiti-task-details.component.css'],
providers: [ActivitiTaskListService],
directives: [ActivitiForm],
pipes: [ AlfrescoPipeTranslate ]
})
export class ActivitiTaskDetails implements OnInit, OnChanges {
@Input()
taskId: string;
taskDetails: TaskDetailsModel;
/**
* Constructor
* @param auth
* @param translate
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
public activiti: ActivitiTaskListService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
}
}
ngOnInit() {
if (this.taskId) {
this.activiti.getTaskDetails(this.taskId).subscribe(
(res: TaskDetailsModel) => {
this.taskDetails = res;
console.log(this.taskDetails);
}
);
}
}
ngOnChanges(change) {
this.loadDetails(this.taskId);
}
loadDetails(id: string) {
if (id) {
this.activiti.getTaskDetails(id).subscribe(
(res: TaskDetailsModel) => {
this.taskDetails = res;
console.log(this.taskDetails);
}
);
}
}
onComplete() {
this.activiti.completeTask(this.taskId).subscribe(
(res) => {
console.log(res);
}
);
}
}

View File

@@ -1,12 +1,23 @@
<div [ngStyle]="locationCss" class="menu-container"> <div class="menu-container">
<ul class="context-menu"> <ul class='mdl-list'>
<li (click)="selectFilter(filter)" *ngFor="let filter of filtersList | async"> <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}} {{filter.name}}
</span>
</li> </li>
</ul> </ul>
</div> </div>
<div *ngIf="currentFilter">
<div *ngIf="!isTaskListEmpty()">
<alfresco-datatable <alfresco-datatable
[data]="tasks" [data]="tasks"
(rowClick)="onRowClick($event)"> (rowClick)="onRowClick($event)">
</alfresco-datatable> </alfresco-datatable>
</div>
<div *ngIf="isTaskListEmpty()">
{{ 'TASK_LIST.MESSAGES.NONE' | translate }}
</div>
</div>
<div *ngIf="!currentFilter">{{ 'TASK_FILTERS.MESSAGES.NONE' | translate }}</div>

View File

@@ -15,8 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnInit} from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit} from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
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';
@@ -31,7 +31,8 @@ declare let __moduleName: string;
moduleId: __moduleName, moduleId: __moduleName,
templateUrl: './activiti-tasklist.component.html', templateUrl: './activiti-tasklist.component.html',
directives: [ALFRESCO_DATATABLE_DIRECTIVES], directives: [ALFRESCO_DATATABLE_DIRECTIVES],
providers: [ActivitiTaskListService] providers: [ActivitiTaskListService],
pipes: [ AlfrescoPipeTranslate ]
}) })
export class ActivitiTaskList implements OnInit { export class ActivitiTaskList implements OnInit {
@@ -39,11 +40,15 @@ export class ActivitiTaskList implements OnInit {
@Input() @Input()
data: DataTableAdapter; data: DataTableAdapter;
@Output()
rowClick: EventEmitter<string> = new EventEmitter<string>();
private filterObserver: Observer<FilterModel>; private filterObserver: Observer<FilterModel>;
filter$: Observable<FilterModel>; filter$: Observable<FilterModel>;
tasks: ObjectDataTableAdapter; tasks: ObjectDataTableAdapter;
currentFilter: FilterModel;
currentTaskId: string; currentTaskId: string;
filtersList: Observable<FilterModel>; filtersList: Observable<FilterModel>;
@@ -89,12 +94,18 @@ export class ActivitiTaskList implements OnInit {
* @param filter * @param filter
*/ */
public selectFilter(filter: FilterModel) { public selectFilter(filter: FilterModel) {
this.currentFilter = filter;
this.filterObserver.next(filter); this.filterObserver.next(filter);
} }
isTaskListEmpty(): boolean {
return this.tasks && this.tasks.getRows().length === 0;
}
onRowClick(event: DataRowEvent) { onRowClick(event: DataRowEvent) {
let item = event; let item = event;
this.currentTaskId = item.value.getValue('id'); this.currentTaskId = item.value.getValue('id');
this.rowClick.emit(this.currentTaskId);
} }
/** /**

View File

@@ -0,0 +1,105 @@
/*!
* @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 details of a task.
*
*
* @returns {TaskDetailsModel} .
*/
export class TaskDetailsModel {
id: string;
name: string;
assignee: User;
priority: number;
adhocTaskCanBeReassigned: number;
category: string;
created: string;
description: string;
dueDate: string;
duration: string;
endDate: string;
executionId: string;
formKey: string;
initiatorCanCompleteTask: boolean = false;
managerOfCandidateGroup: boolean = false;
memberOfCandidateGroup: boolean = false;
memberOfCandidateUsers: boolean = false;
involvedPeople: User [];
parentTaskId: string;
parentTaskName: string;
processDefinitionCategory: string;
processDefinitionDeploymentId: string;
processDefinitionDescription: string;
processDefinitionId: string;
processDefinitionKey: string;
processDefinitionName: string;
processDefinitionVersion: number = 0;
processInstanceId: string;
processInstanceName: string;
processInstanceStartUserId: string;
taskDefinitionKey: string;
constructor(obj: any) {
this.id = obj.id;
this.name = obj.name;
this.priority = obj.priority;
this.assignee = new User(obj.assignee.id, obj.assignee.email, obj.assignee.firstName, obj.assignee.lastName);
this.adhocTaskCanBeReassigned = obj.adhocTaskCanBeReassigned;
this.created = obj.created;
this.description = obj.description;
this.dueDate = obj.dueDate;
this.duration = obj.duration;
this.endDate = obj.endDate;
this.executionId = obj.executionId;
this.formKey = obj.formKey;
this.initiatorCanCompleteTask = obj.initiatorCanCompleteTask;
this.managerOfCandidateGroup = obj.managerOfCandidateGroup;
this.memberOfCandidateGroup = obj.memberOfCandidateGroup;
this.memberOfCandidateUsers = obj.memberOfCandidateUsers;
this.involvedPeople = obj.involvedPeople;
this.parentTaskId = obj.parentTaskId;
this.parentTaskName = obj.parentTaskName;
this.processDefinitionCategory = obj.processDefinitionCategory;
this.processDefinitionDeploymentId = obj.processDefinitionDeploymentId;
this.processDefinitionDescription = obj.processDefinitionDescription;
this.processDefinitionId = obj.processDefinitionId;
this.processDefinitionKey = obj.processDefinitionKey;
this.processDefinitionName = obj.processDefinitionName;
this.processDefinitionVersion = obj.processDefinitionVersion;
this.processInstanceId = obj.processInstanceId;
this.processInstanceName = obj.processInstanceName;
this.processInstanceStartUserId = obj.processInstanceStartUserId;
this.taskDefinitionKey = obj.taskDefinitionKey;
}
}
export class User {
id: number;
email: string;
firstName: string;
lastName: string;
constructor(id: number, email: string, firstName: string, lastName: string) {
this.id = id;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
}

View File

@@ -20,6 +20,7 @@ import { AlfrescoSettingsService } from 'ng2-alfresco-core';
import { Http, Headers, RequestOptions, Response } from '@angular/http'; import { Http, Headers, RequestOptions, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { FilterModel } from '../models/filter.model'; import { FilterModel } from '../models/filter.model';
import { TaskDetailsModel } from '../models/task-details.model';
@Injectable() @Injectable()
export class ActivitiTaskListService { export class ActivitiTaskListService {
@@ -45,10 +46,13 @@ export class ActivitiTaskListService {
*/ */
getTasks(filter: FilterModel): Observable<any> { getTasks(filter: FilterModel): Observable<any> {
let data: any = {}; let data: any = {};
data.filterId = filter.id; // data.filterId = filter.id;
data.filter = filter.filter; // data.filter = filter.filter;
data = filter.filter;
data.text = filter.filter.name;
data = JSON.stringify(data); data = JSON.stringify(data);
return Observable.fromPromise(this.callApiTasksFiltered(data)) return Observable.fromPromise(this.callApiTasksFiltered(data))
.map((res: Response) => { .map((res: Response) => {
return res.json(); return res.json();
@@ -56,12 +60,28 @@ export class ActivitiTaskListService {
.catch(this.handleError); .catch(this.handleError);
} }
getTaskDetails(id: string): Observable<TaskDetailsModel> {
return Observable.fromPromise(this.callApiTaskDetails(id))
.map(res => res.json())
.map((details: any) => {
return new TaskDetailsModel(details);
})
.catch(this.handleError);
}
completeTask(id: string): Observable<TaskDetailsModel> {
return Observable.fromPromise(this.callApiCompleteTask(id))
.catch(this.handleError);
}
private callApiTasksFiltered(data: Object) { private callApiTasksFiltered(data: Object) {
let url = this.alfrescoSettingsService.getBPMApiBaseUrl() + '/rest/filter/tasks'; let url = this.alfrescoSettingsService.getBPMApiBaseUrl() + '/api/enterprise/tasks/query';
// let url = 'http://localhost:9999/activiti-app/app/rest/filter/tasks';
let headers = new Headers({ let headers = new Headers({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Cache-Control': 'no-cache' 'Cache-Control': 'no-cache'
}); });
// let body = JSON.stringify(data);
let options = new RequestOptions({headers: headers}); let options = new RequestOptions({headers: headers});
return this.http return this.http
@@ -69,7 +89,8 @@ export class ActivitiTaskListService {
} }
private callApiTaskFilters() { private callApiTaskFilters() {
let url = this.alfrescoSettingsService.getBPMApiBaseUrl() + '/rest/filters/tasks'; let url = this.alfrescoSettingsService.getBPMApiBaseUrl() + '/api/enterprise/filters/tasks';
// let url = 'http://localhost:9999/activiti-app/app/rest/filters/tasks';
let headers = new Headers({ let headers = new Headers({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Cache-Control': 'no-cache' 'Cache-Control': 'no-cache'
@@ -80,6 +101,32 @@ export class ActivitiTaskListService {
.get(url, options).toPromise(); .get(url, options).toPromise();
} }
private callApiTaskDetails(id: string) {
let url = this.alfrescoSettingsService.getBPMApiBaseUrl() + `/api/enterprise/tasks/${id}`;
// let url = 'http://localhost:9999/activiti-app/app/rest/tasks/' + id;
let headers = new Headers({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({headers: headers});
return this.http
.get(url, options).toPromise();
}
private callApiCompleteTask(id: string) {
let url = this.alfrescoSettingsService.getBPMApiBaseUrl() + `/api/enterprise/tasks/${id}/action/complete`;
// let url = `http://localhost:9999/activiti-app/app/rest/tasks/${id}/action/complete`;
let headers = new Headers({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({headers: headers});
return this.http
.put(url, options).toPromise();
}
/** /**
* The method write the error in the console browser * The method write the error in the console browser