Merge branch 'development' into dev-denys-dts

# Conflicts:
#	demo-shell-ng2/systemjs.config.js
#	ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts
#	ng2-components/ng2-alfresco-upload/src/services/upload.service.ts
#
ng2-components/ng2-alfresco-viewer/src/componets/viewer.component.spec.t
s
#	ng2-components/ng2-alfresco-viewer/src/componets/viewer.component.ts
This commit is contained in:
Denys Vuika 2016-09-05 09:10:08 +01:00
commit c037b91c9c
176 changed files with 4641 additions and 1173 deletions

View File

@ -28,8 +28,8 @@ env:
- MODULE=ng2-alfresco-viewer
- MODULE=ng2-alfresco-webscript
- MODULE=ng2-activiti-form
- MODULE=ng2-activiti-processlist
- MODULE=ng2-activiti-tasklist
- MODULE=ng2-activiti-processlist
before_script:
- if ([ "$MODULE" != "ng2-alfresco-core" ]); then
@ -38,9 +38,12 @@ before_script:
- if ([ "$MODULE" == "ng2-alfresco-documentlist" ] || [ "$MODULE" == "ng2-alfresco-webscript" ] || [ "$MODULE" == "ng2-activiti-processlist" ] || [ "$MODULE" == "ng2-activiti-tasklist" ]); then
(cd ng2-components/ng2-alfresco-datatable; npm link ng2-alfresco-core; npm install; npm link);
fi
- if ([ "$MODULE" == "ng2-activiti-tasklist" ]); then
- if ([ "$MODULE" == "ng2-activiti-tasklist" ] || [ "$MODULE" == "ng2-activiti-processlist" ]); then
(cd ng2-components/ng2-activiti-form; npm link ng2-alfresco-core; npm install; npm link);
fi
- if ([ "$MODULE" == "ng2-activiti-processlist" ]); then
(cd ng2-components/ng2-activiti-tasklist; npm link ng2-alfresco-core; npm link ng2-alfresco-datatable; npm link ng2-activiti-form; npm install; npm link);
fi
- cd ng2-components/$MODULE;
- if ([ "$MODULE" != "ng2-alfresco-core" ]); then
npm link ng2-alfresco-core;
@ -51,6 +54,9 @@ before_script:
- if ([ "$MODULE" == "ng2-activiti-tasklist" ]); then
npm link ng2-activiti-form;
fi
- if ([ "$MODULE" == "ng2-activiti-processlist" ]); then
npm link ng2-activiti-tasklist;
fi
- npm install;
- npm run travis
- ls -ltrh ./node_modules/

View File

@ -52,6 +52,8 @@ The following is a list of some of the components that you can use when building
- [Core library](ng2-components/ng2-alfresco-core/README.md)
- [DataTable](ng2-components/ng2-alfresco-datatable/README.md)
- [DocumentList](ng2-components/ng2-alfresco-documentlist/README.md)
- Activiti [ProcessList](ng2-components/ng2-activiti-processlist/README.md) and [TaskList](ng2-components/ng2-activiti-tasklist/README.md)
- [Activiti Form](ng2-components/ng2-activiti-form/README.md)
- [Viewer](ng2-components/ng2-alfresco-viewer/README.md)
- [Login](ng2-components/ng2-alfresco-login/README.md)
- [Upload](ng2-components/ng2-alfresco-upload/README.md)

View File

@ -27,6 +27,8 @@ install:
- IF %COMPONENT_NAME% NEQ ng2-alfresco-core (cd ng2-components/ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-alfresco-documentlist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-activiti-form && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-activiti-tasklist && npm link ng2-alfresco-core && npm link ng2-alfresco-datatable && npm link ng2-activiti-form && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-activiti-form && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-alfresco-webscript (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
@ -34,6 +36,7 @@ install:
- IF %COMPONENT_NAME% NEQ ng2-alfresco-core (npm link ng2-alfresco-core)
- IF %COMPONENT_NAME% EQU ng2-alfresco-documentlist (npm link ng2-alfresco-datatable)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (npm link ng2-alfresco-datatable)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (npm link ng2-activiti-tasklist)
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (npm link ng2-alfresco-datatable && npm link ng2-activiti-form)
- IF %COMPONENT_NAME% EQU ng2-alfresco-webscript (npm link ng2-alfresco-datatable)
- npm install

View File

@ -28,6 +28,7 @@ import {
AboutComponent,
FormViewer
} from './components/index';
import { FormNodeViewer } from './components/activiti/form-node-viewer.component';
export const routes: RouterConfig = [
{ path: 'home', component: FilesComponent },
@ -39,6 +40,7 @@ export const routes: RouterConfig = [
{ path: 'search', component: SearchComponent },
{ path: 'activiti', component: ActivitiDemoComponent },
{ path: 'activiti/tasks/:id', component: FormViewer },
{ path: 'activiti/tasksnode/:id', component: FormNodeViewer },
{ path: 'webscript', component: WebscriptComponent },
{ path: 'about', component: AboutComponent }
];

View File

@ -13,16 +13,17 @@
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column mdl-shadow--2dp">
<span>Task Filters</span>
<activiti-filters (filterClick)="onFilterClick($event)"></activiti-filters>
<activiti-filters (filterClick)="onTaskFilterClick($event)"></activiti-filters>
</div>
<div class="mdl-cell mdl-cell--3-col task-column mdl-shadow--2dp">
<span>Task List</span>
<activiti-tasklist *ngIf="isTaskListSelected()" [taskFilter]="taskFilter" [schemaColumn]="schemaColumn"
(rowClick)="onRowClick($event)" #activititasklist></activiti-tasklist>
<activiti-tasklist *ngIf="isTaskListSelected()" [taskFilter]="taskFilter" [schemaColumn]="taskSchemaColumns"
(rowClick)="onTaskRowClick($event)" #activititasklist></activiti-tasklist>
</div>
<div class="mdl-cell mdl-cell--7-col task-column mdl-shadow--2dp">
<span>Task Details</span>
<activiti-task-details [taskId]="currentTaskId" #activitidetails></activiti-task-details>
<activiti-task-details [taskId]="currentTaskId" (formCompleted)="onFormCompleted($event)"
#activitidetails></activiti-task-details>
</div>
</div>
</div>
@ -33,12 +34,17 @@
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column">
<span>Process Filters</span>
<activiti-start-process [appId]="appId"></activiti-start-process>
<activiti-process-filters (filterClick)="onProcessFilterClick($event)"></activiti-process-filters>
</div>
<div class="mdl-cell mdl-cell--3-col task-column">
<span>Process List</span>
<activiti-process-instance-list [filter]="processFilter" [schemaColumn]="processSchemaColumns"
(rowClick)="onProcessRowClick($event)" #activitiprocesslist></activiti-process-instance-list>
</div>
<div class="mdl-cell mdl-cell--7-col task-column">
<span>Process Details</span>
<activiti-process-instance-details [processInstanceId]="currentProcessInstanceId" (taskFormCompleted)="taskFormCompleted()" (processCancelled)="processCancelled()" #activitiprocessdetails></activiti-process-instance-details>
</div>
</div>
</div>

View File

@ -15,8 +15,9 @@
* limitations under the License.
*/
import { Component, AfterViewChecked, ViewChild } from '@angular/core';
import { Component, AfterViewChecked, ViewChild, Input } from '@angular/core';
import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist';
import { ACTIVITI_PROCESSLIST_DIRECTIVES } from 'ng2-activiti-processlist';
import { ActivitiForm } from 'ng2-activiti-form';
declare let __moduleName: string;
@ -27,7 +28,7 @@ declare var componentHandler;
selector: 'activiti-demo',
templateUrl: './activiti-demo.component.html',
styleUrls: ['./activiti-demo.component.css'],
directives: [ALFRESCO_TASKLIST_DIRECTIVES, ActivitiForm]
directives: [ALFRESCO_TASKLIST_DIRECTIVES, ACTIVITI_PROCESSLIST_DIRECTIVES, ActivitiForm]
})
export class ActivitiDemoComponent implements AfterViewChecked {
@ -39,11 +40,23 @@ export class ActivitiDemoComponent implements AfterViewChecked {
@ViewChild('activititasklist')
activititasklist: any;
currentTaskId: string;
@ViewChild('activitiprocesslist')
activitiprocesslist: any;
schemaColumn: any [] = [];
@ViewChild('activitiprocessdetails')
activitiprocessdetails: any;
currentTaskId: string;
currentProcessInstanceId: string;
taskSchemaColumns: any [] = [];
processSchemaColumns: any [] = [];
taskFilter: any;
processFilter: any;
@Input()
appId: string;
setChoice($event) {
this.currentChoice = $event.target.value;
@ -58,23 +71,50 @@ export class ActivitiDemoComponent implements AfterViewChecked {
}
constructor() {
console.log('Activiti demo component');
this.schemaColumn = [
this.taskSchemaColumns = [
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}
// {type: 'text', key: 'created', title: 'Created', sortable: true}
];
this.processSchemaColumns = [
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}
];
}
onFilterClick(event: any) {
onTaskFilterClick(event: any) {
this.taskFilter = event;
this.activititasklist.load(this.taskFilter);
}
onRowClick(taskId) {
onProcessFilterClick(event: any) {
this.processFilter = event.filter;
this.activitiprocesslist.load(this.processFilter);
}
onTaskRowClick(taskId) {
this.currentTaskId = taskId;
this.activitidetails.loadDetails(this.currentTaskId);
}
onProcessRowClick(processInstanceId) {
this.currentProcessInstanceId = processInstanceId;
this.activitiprocessdetails.load(this.currentProcessInstanceId);
}
processCancelled(data: any) {
this.currentProcessInstanceId = null;
this.activitiprocesslist.reload();
}
taskFormCompleted(data: any) {
this.activitiprocesslist.reload();
}
onFormCompleted(form) {
this.activititasklist.load(this.taskFilter);
this.currentTaskId = null;
this.activitidetails.loadDetails(this.currentTaskId);
}
ngAfterViewChecked() {
// workaround for MDL issues with dynamic components
if (componentHandler) {

View File

@ -0,0 +1,3 @@
.activiti-form-viewer {
margin: 10px;
}

View File

@ -0,0 +1,6 @@
<div class="activiti-form-viewer" *ngIf="nodeId">
<activiti-form [nodeId]="nodeId"
[saveMetadata]="true"
[path]="'/Sites/swsdp/documentLibrary'">
</activiti-form>
</div>

View File

@ -0,0 +1,62 @@
/*!
* @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, OnInit, OnDestroy, AfterViewChecked } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form';
import { Subscription } from 'rxjs/Rx';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'form-node-viewer',
templateUrl: './form-node-viewer.component.html',
styleUrls: ['./form-node-viewer.component.css'],
directives: [ActivitiForm],
providers: [FormService, EcmModelService, NodeService]
})
export class FormNodeViewer implements OnInit, OnDestroy, AfterViewChecked {
nodeId: string;
private sub: Subscription;
constructor(private formService: FormService,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.nodeId = params['id'];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
ngAfterViewChecked() {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
}
}
}

View File

@ -1,3 +1,11 @@
<div class="activiti-form-viewer" *ngIf="taskId">
<activiti-form [taskId]="taskId"></activiti-form>
<!--<activiti-form [formName]="'activitiForms:patientFolder'"-->
<!--[saveMetadata]="true"-->
<!--[path]="'/Sites/swsdp/documentLibrary'"-->
<!--[nameNode]="'test'"></activiti-form>-->
<!--<activiti-form [nodeId]="'e280be3a-6584-45a1-8bb5-89bfe070262e'"-->
<!--[saveMetadata]="true"-->
<!--[path]="'/Sites/swsdp/documentLibrary'">-->
<!--</activiti-form>-->
</div>

View File

@ -17,7 +17,7 @@
import { Component, OnInit, OnDestroy, AfterViewChecked } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivitiForm, FormService } from 'ng2-activiti-form';
import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form';
import { Subscription } from 'rxjs/Rx';
declare let __moduleName: string;
@ -29,7 +29,7 @@ declare var componentHandler;
templateUrl: './form-viewer.component.html',
styleUrls: ['./form-viewer.component.css'],
directives: [ActivitiForm],
providers: [FormService]
providers: [FormService, EcmModelService, NodeService]
})
export class FormViewer implements OnInit, OnDestroy, AfterViewChecked {

View File

@ -3,6 +3,7 @@
[showUploadDialog]="true"
[currentFolderPath]="currentPath"
[uploaddirectory]=""
[versioning] = "versioning"
(onSuccess)="documentList.reload()">
<alfresco-document-list-breadcrumb
[currentFolderPath]="currentPath"
@ -91,7 +92,7 @@
handler="delete">
</content-action>
<content-action
target="document"
target="folder"
title="Activiti: View Form"
(execute)="viewActivitiForm($event)">
</content-action>
@ -147,6 +148,13 @@
</label>
</p>
<p style="width:250px;margin: 20px;">
<label for="switch-versioning" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch-versioning" class="mdl-switch__input" (change)="toggleVersioning()">
<span class="mdl-switch__label">Versioning</span>
</label>
</p>
<h5>Upload</h5>
<br>
<div *ngIf="acceptedFilesTypeShow">
@ -160,6 +168,7 @@
[currentFolderPath]="currentPath"
[multipleFiles]="multipleFileUpload"
[uploadFolders]="folderUpload"
[versioning] = "versioning"
(onSuccess)="documentList.reload()">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</alfresco-upload-button>
@ -171,6 +180,7 @@
acceptedFilesType="{{acceptedFilesType}}"
[multipleFiles]="multipleFileUpload"
[uploadFolders]="folderUpload"
[versioning] = "versioning"
(onSuccess)="documentList.reload()">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</alfresco-upload-button>

View File

@ -63,6 +63,7 @@ export class FilesComponent implements OnInit {
multipleFileUpload: boolean = false;
folderUpload: boolean = false;
acceptedFilesTypeShow: boolean = false;
versioning: boolean = false;
acceptedFilesType: string = '.jpg,.pdf,.js';
@ -119,6 +120,11 @@ export class FilesComponent implements OnInit {
return this.acceptedFilesTypeShow;
}
toggleVersioning() {
this.versioning = !this.versioning;
return this.versioning;
}
ngOnInit() {
this.formService.getProcessDefinitions().subscribe(
defs => this.setupBpmActions(defs || []),
@ -127,7 +133,7 @@ export class FilesComponent implements OnInit {
}
viewActivitiForm(event?: any) {
this.router.navigate(['/activiti/tasks', '1']);
this.router.navigate(['/activiti/tasksnode', event.value.entry.id]);
}
private setupBpmActions(actions: any[]) {

View File

@ -16,13 +16,9 @@
*/
import { Component, EventEmitter, Output } from '@angular/core';
import { Router } from '@angular/router';
import { ALFRESCO_SEARCH_DIRECTIVES } from 'ng2-alfresco-search';
import { VIEWERCOMPONENT } from 'ng2-alfresco-viewer';
import {
AlfrescoAuthenticationService,
AlfrescoContentService
} from 'ng2-alfresco-core';
import { AlfrescoAuthenticationService } from 'ng2-alfresco-core';
declare let __moduleName: string;
@ -30,52 +26,36 @@ declare let __moduleName: string;
moduleId: __moduleName,
selector: 'search-bar',
templateUrl: './search-bar.component.html',
styles: [`
`],
directives: [ ALFRESCO_SEARCH_DIRECTIVES, VIEWERCOMPONENT ]
directives: [ALFRESCO_SEARCH_DIRECTIVES, VIEWERCOMPONENT]
})
export class SearchBarComponent {
urlFile: string;
fileName: string;
mimeType: string;
fileNodeId: string;
fileShowed: boolean = false;
searchTerm: string = '';
@Output()
expand = new EventEmitter();
constructor(
public router: Router,
public auth: AlfrescoAuthenticationService,
public contentService: AlfrescoContentService
) {
constructor(public auth: AlfrescoAuthenticationService) {
}
isLoggedIn(): boolean {
return this.auth.isLoggedIn();
}
/**
* Called when a new search term is submitted
*
* @param params Parameters relating to the search
*/
searchTermChange(params) {
this.router.navigate(['Search', {
q: params.value
}]);
}
onFileClicked(event) {
if (event.value.entry.isFile) {
this.fileName = event.value.entry.name;
this.mimeType = event.value.entry.content.mimeType;
this.urlFile = this.contentService.getContentUrl(event.value);
this.fileNodeId = event.value.entry.id;
this.fileShowed = true;
}
}
searchTermChange(event) {
console.log('Search term changed', event);
this.searchTerm = event.value;
}
onExpandToggle(event) {
this.expand.emit(event);
}

View File

@ -3,6 +3,6 @@
<alfresco-search (preview)="onFileClicked($event)"></alfresco-search>
</div>
<alfresco-viewer [(showViewer)]="previewActive" [urlFile]="previewContentUrl" [fileName]="previewName" [mimeType]="previewMimeType" [overlayMode]="true">
<alfresco-viewer [(showViewer)]="fileShowed" [fileNodeId]="fileNodeId" [overlayMode]="true">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</alfresco-viewer>

View File

@ -52,10 +52,7 @@ declare let __moduleName: string;
})
export class SearchComponent {
previewContentUrl: string;
previewName: string;
previewMimeType: string;
previewActive: boolean = false;
fileShowed: boolean = false;
fileNodeId: string;
constructor(public contentService: AlfrescoContentService) {
@ -64,7 +61,7 @@ export class SearchComponent {
onFileClicked(event) {
if (event.value.entry.isFile) {
this.fileNodeId = event.value.entry.id;
this.previewActive = true;
this.fileShowed = true;
}
}
}

View File

@ -19,9 +19,9 @@ import { bootstrap } from '@angular/platform-browser-dynamic';
import { HTTP_PROVIDERS } from '@angular/http';
import { ALFRESCO_SEARCH_PROVIDERS } from 'ng2-alfresco-search';
import { ALFRESCO_CORE_PROVIDERS } from 'ng2-alfresco-core';
import { ATIVITI_FORM_PROVIDERS } from 'ng2-activiti-form';
import { UploadService } from 'ng2-alfresco-upload';
import { AppComponent } from './app.component';
import { appRouterProviders } from './app.routes';
bootstrap(AppComponent, [
@ -29,5 +29,6 @@ bootstrap(AppComponent, [
HTTP_PROVIDERS,
ALFRESCO_CORE_PROVIDERS,
ALFRESCO_SEARCH_PROVIDERS,
UploadService
UploadService,
ATIVITI_FORM_PROVIDERS
]).catch(err => console.error(err));

View File

@ -4,7 +4,7 @@
"DOCUMENT_LIST": {
"COLUMNS": {
"DISPLAY_NAME": "Display name",
"CREATED_BY": "mario",
"CREATED_BY": "Created by",
"CREATED_ON": "Created on"
},
"ACTIONS": {

View File

@ -16,6 +16,10 @@
<link href="node_modules/flag-icon-css/css/flag-icon.min.css" rel="stylesheet">
<!-- 1. Load libraries -->
<!-- Polyfill(s) for Safari (pre-10.x) -->
<script src="node_modules/intl/dist/Intl.min.js"></script>
<script src="node_modules/intl/locale-data/jsonp/en.js"></script>
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>

View File

@ -1,7 +1,7 @@
{
"name": "Alfresco-Angular2-Demo",
"description": "Demo shell for Alfresco Angular2 components",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
@ -73,18 +73,20 @@
"material-design-icons": "2.2.3",
"material-design-lite": "1.1.3",
"ng2-translate": "2.2.0",
"pdfjs-dist": "1.5.258",
"pdfjs-dist": "1.5.404",
"flag-icon-css": "2.3.0",
"ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0",
"ng2-alfresco-documentlist": "0.2.0",
"ng2-alfresco-login": "0.2.0",
"ng2-alfresco-search": "0.2.0",
"ng2-alfresco-upload": "0.2.0",
"ng2-alfresco-viewer": "0.2.0",
"ng2-activiti-form": "0.2.0",
"ng2-activiti-tasklist": "0.2.0",
"ng2-alfresco-webscript": "0.2.0"
"intl": "1.2.4",
"ng2-alfresco-core": "0.3.0",
"ng2-alfresco-datatable": "0.3.0",
"ng2-alfresco-documentlist": "0.3.0",
"ng2-alfresco-login": "0.3.0",
"ng2-alfresco-search": "0.3.0",
"ng2-alfresco-upload": "0.3.0",
"ng2-alfresco-viewer": "0.3.0",
"ng2-activiti-form": "0.3.0",
"ng2-activiti-tasklist": "0.3.0",
"ng2-activiti-processlist": "0.3.0",
"ng2-alfresco-webscript": "0.3.0"
},
"devDependencies": {
"concurrently": "2.0.0",

View File

@ -22,6 +22,7 @@
'ng2-alfresco-webscript': 'node_modules/ng2-alfresco-webscript/dist',
'ng2-activiti-tasklist': 'node_modules/ng2-activiti-tasklist/dist',
'alfresco-js-api': 'node_modules/alfresco-js-api/dist'
'ng2-activiti-processlist': 'node_modules/ng2-activiti-processlist/dist'
};
// packages tells the System loader how to load when no filename and/or no extension
var packages = {
@ -39,6 +40,7 @@
'ng2-alfresco-upload': { main: 'index.js', defaultExtension: 'js'},
'ng2-alfresco-viewer': { main: 'index.js', defaultExtension: 'js'},
'ng2-activiti-form': { main: 'index.js', defaultExtension: 'js'},
'ng2-activiti-processlist': { main: 'index.js', defaultExtension: 'js'},
'ng2-activiti-tasklist': { main: 'index.js', defaultExtension: 'js'},
'ng2-alfresco-webscript': { main: 'index.js', defaultExtension: 'js'},
'alfresco-js-api': { main: 'alfresco-js-api.js', defaultExtension: 'js'}

View File

@ -78,6 +78,38 @@ Only form definition will be fetched
</activiti-form>
```
### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form. If there are no form
definied in activiti for the type of the node, a new form will be automaticaly created in activiti.
```html
<activiti-form
[nodeId]="'e280be3a-6584-45a1-8bb5-89bfe070262e'">
</activiti-form>
```
### Display form definition by form name, and store the form field as metadata. The param nameNode is optional.
```html
<activiti-form
[formName]="'activitiForms:patientFolder'"
[saveMetadata]="true"
[path]="'/Sites/swsdp/documentLibrary'"
[nameNode]="'test'">
</activiti-form>
```
### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form, and store the form field
as metadata. The param nameNode is optional.
```html
<activiti-form
[nodeId]="'e280be3a-6584-45a1-8bb5-89bfe070262e'"
[saveMetadata]="true"
[path]="'/Sites/swsdp/documentLibrary'"
[nameNode]="'test'">
</activiti-form>
```
## Configuration
### Properties
@ -95,7 +127,14 @@ The recommended set of properties can be found in the following table:
| showSaveButton | boolean | true | Toggle rendering of the `Save` outcome button. |
| readOnly | boolean | false | Toggle readonly state of the form. Enforces all form widgets render readonly if enabled. |
| showRefreshButton | boolean | true | Toggle rendering of the `Refresh` button. |
| saveMetadata | boolean | false | Store the value of the form as metadata. |
| path | string | | Path of the folder where to store the metadata. |
| nameNode (optional) | string | true | Name to assign to the new node where the metadata are stored. |
* {path} string - path of the folder where the to store the metadata
*
* {nameNode} string (optional) - name of the node stored, if not defined the node will be sotred with an uuid as name
#### Advanced properties
The following properties are for complex customisation purposes:
@ -120,7 +159,7 @@ All `form*` events receive an instance of the `FormModel` as event argument for
```html
<activiti-form
[taskId]="selectedTask?.id"
formSaved="onFormSaved($event)">
(formSaved)="onFormSaved($event)">
</activiti-form>
```

View File

@ -15,6 +15,19 @@
* limitations under the License.
*/
import { FormService } from './src/services/form.service';
import { EcmModelService } from './src/services/ecm-model.service';
import { NodeService } from './src/services/node.service';
export * from './src/components/activiti-form.component';
export * from './src/services/form.service';
export * from './src/components/widgets/index';
export * from './src/services/ecm-model.service';
export * from './src/services/node.service';
export const ATIVITI_FORM_PROVIDERS: [any] = [
FormService,
EcmModelService,
NodeService
];

View File

@ -1,7 +1,7 @@
{
"name": "ng2-activiti-form",
"description": "Alfresco Activiti Form Component for Angular 2",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
@ -65,7 +65,7 @@
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0"
"ng2-alfresco-core": "0.3.0"
},
"peerDependencies": {
"material-design-icons": "^2.2.3",

View File

@ -1,6 +1,6 @@
<div>
<div *ngIf="!hasForm()">
<h3 style="text-align: center">Please select a Visit</h3>
<h3 style="text-align: center">Please select a Task</h3>
</div>
<div *ngIf="hasForm()">
<div class="mdl-card mdl-shadow--2dp activiti-form-container">

View File

@ -40,8 +40,8 @@ describe('ActivitiForm', () => {
]);
window['componentHandler'] = componentHandler;
formService = new FormService(null);
formComponent = new ActivitiForm(formService, visibilityService);
formService = new FormService(null, null);
formComponent = new ActivitiForm(formService, visibilityService, null, null, null);
});
it('should upgrade MDL content on view checked', () => {

View File

@ -23,9 +23,10 @@ import {
Output,
EventEmitter
} from '@angular/core';
import { MATERIAL_DESIGN_DIRECTIVES } from 'ng2-alfresco-core';
import { MATERIAL_DESIGN_DIRECTIVES, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { EcmModelService } from './../services/ecm-model.service';
import { FormService } from './../services/form.service';
import { NodeService } from './../services/node.service';
import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index';
import { TabsWidget } from './widgets/tabs/tabs.widget';
@ -38,19 +39,23 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic
/**
* @Input
* ActivitiForm can show 3 forms searching by 3 type of params:
* ActivitiForm can show 4 types of forms searching by 4 type of params:
* 1) Form attached to a task passing the {taskId}.
*
* 2) Form that are only defined with the {formId} (in this case you receive only the form definition and the form will not be
* attached to any process, useful in case you want to use ActivitiForm as form designer), in this case you can pass also other 2
* parameters:
* - {saveOption} as parameter to tell what is the function to call on the save action.
* - {data} to fill the form field with some data, the id of the form must to match the name of the field of the provided data object.
*
* 3) Form that are only defined with the {formName} (in this case you receive only the form definition and the form will not be
* attached to any process, useful in case you want to use ActivitiForm as form designer),
* in this case you can pass also other 2 parameters:
* - {saveOption} as parameter to tell what is the function to call on the save action.
* - {data} to fill the form field with some data, the id of the form must to match the name of the field of the provided data object.
*
* 4) Form that show the metadata of a {nodeId}
*
* {showTitle} boolean - to hide the title of the form pass false, default true;
*
* {showRefreshButton} boolean - to hide the refresh button of the form pass false, default true;
@ -59,6 +64,12 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic
*
* {showSaveButton} boolean - to hide the save button of the form pass false, default true;
*
* {saveMetadata} boolean - store the value of the form as metadata, default false;
*
* {path} string - path of the folder where to store the metadata;
*
* {nameNode} string (optional) - Name to assign to the new node where the metadata are stored;
*
* @Output
* {formLoaded} EventEmitter - This event is fired when the form is loaded, it pass all the value in the form.
* {formSaved} EventEmitter - This event is fired when the form is saved, it pass all the value in the form.
@ -72,7 +83,7 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic
templateUrl: './activiti-form.component.html',
styleUrls: ['./activiti-form.component.css'],
directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget, TabsWidget],
providers: [FormService, WidgetVisibilityService]
providers: [EcmModelService, FormService, WidgetVisibilityService, NodeService]
})
export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
@ -83,15 +94,27 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
@Input()
taskId: string;
@Input()
nodeId: string;
@Input()
formId: string;
@Input()
formName: string;
@Input()
saveMetadata: boolean = false;
@Input()
data: FormValues;
@Input()
path: string;
@Input()
nameNode: string;
@Input()
showTitle: boolean = true;
@ -124,7 +147,10 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
debugMode: boolean = false;
constructor(private formService: FormService,
private visibilityService: WidgetVisibilityService) {
private visibilityService: WidgetVisibilityService,
private authService: AlfrescoAuthenticationService,
private ecmModelService: EcmModelService,
private nodeService: NodeService) {
}
hasForm(): boolean {
@ -154,8 +180,12 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
}
ngOnInit() {
if (this.nodeId) {
this.loadActivitiFormForEcmNode();
} else {
this.loadForm();
}
}
ngAfterViewChecked() {
this.setupMaterialComponents();
@ -208,6 +238,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
if (outcome.id === ActivitiForm.CUSTOM_OUTCOME_ID) {
this.formSaved.emit(this.form);
this.storeFormAsMetadata();
return true;
}
} else {
@ -275,7 +306,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
.getFormDefinitionById(formId)
.subscribe(
form => {
// console.log('Get Form By definition Id', form);
this.formName = form.name;
this.form = this.parseForm(form);
this.formLoaded.emit(this.form);
},
@ -306,7 +337,10 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
this.formService
.saveTaskForm(this.form.taskId, this.form.values)
.subscribe(
() => this.formSaved.emit(this.form),
() => {
this.formSaved.emit(this.form);
this.storeFormAsMetadata();
},
this.handleError
);
}
@ -317,13 +351,16 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
this.formService
.completeTaskForm(this.form.taskId, this.form.values, outcome)
.subscribe(
() => this.formCompleted.emit(this.form),
() => {
this.formCompleted.emit(this.form);
this.storeFormAsMetadata();
},
this.handleError
);
}
}
handleError(err: any) {
handleError(err: any): any {
console.log(err);
}
@ -345,7 +382,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
*/
getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] {
return [
new FormOutcomeModel(form, { id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true })
new FormOutcomeModel(form, {id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true})
];
}
@ -354,4 +391,41 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
this.visibilityService.updateVisibilityForForm(field.form);
}
}
private loadActivitiFormForEcmNode(): void {
this.nodeService.getNodeMetadata(this.nodeId).subscribe(data => {
this.data = data.metadata;
this.loadFormFromActiviti(data.nodeType);
},
this.handleError);
}
public loadFormFromActiviti(nodeType: string): any {
this.formService.searchFrom(nodeType).subscribe(
form => {
if (!form) {
this.formService.createFormFromNodeType(nodeType).subscribe(formMetadata => {
this.loadFormFromFormId(formMetadata.id);
});
} else {
this.loadFormFromFormId(form.id);
}
},
this.handleError
);
}
private loadFormFromFormId(formId: string) {
this.formId = formId;
this.loadForm();
}
private storeFormAsMetadata() {
if (this.saveMetadata) {
this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe(type => {
this.nodeService.createNodeMetadata(type.nodeType || type.entry.prefixedName, EcmModelService.MODEL_NAMESPACE, this.form.values, this.path, this.nameNode);
}, this.handleError
);
}
}
}

View File

@ -43,6 +43,9 @@
<div *ngSwitchCase="'readonly-text'">
<display-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-text-widget>
</div>
<div *ngSwitchCase="'upload'">
<upload-widget [field]="field" (fieldChanged)="fieldChanged($event);"></upload-widget>
</div>
<div *ngSwitchDefault>
<span>UNKNOWN WIDGET TYPE: {{field.type}}</span>
</div>

View File

@ -23,6 +23,7 @@ export class FormFieldTypes {
static RADIO_BUTTONS: string = 'radio-buttons';
static DISPLAY_VALUE: string = 'readonly';
static READONLY_TEXT: string = 'readonly-text';
static UPLOAD: string = 'upload';
static READONLY_TYPES: string[] = [
FormFieldTypes.HYPERLINK,

View File

@ -129,7 +129,9 @@ export class FormFieldModel extends FormWidgetModel {
}
updateForm() {
if (this.type === FormFieldTypes.DROPDOWN) {
switch (this.type) {
case FormFieldTypes.DROPDOWN:
/*
This is needed due to Activiti reading dropdown values as string
but saving back as object: { id: <id>, name: <name> }
@ -142,7 +144,8 @@ export class FormFieldModel extends FormWidgetModel {
this.form.values[this.id] = entry[0];
}
}
} else if (this.type === FormFieldTypes.RADIO_BUTTONS) {
break;
case FormFieldTypes.RADIO_BUTTONS:
/*
This is needed due to Activiti issue related to reading radio button values as value string
but saving back as object: { id: <id>, name: <name> }
@ -153,7 +156,15 @@ export class FormFieldModel extends FormWidgetModel {
} else if (this.options.length > 0) {
this.form.values[this.id] = this.options[0];
}
break;
case FormFieldTypes.UPLOAD:
if (this.value && this.value.length > 0) {
this.form.values[this.id] = `${this.value[0].id}`;
} else {
this.form.values[this.id] = null;
}
break;
default:
if (!FormFieldTypes.isReadOnlyType(this.type)) {
this.form.values[this.id] = this.value;
}

View File

@ -1,4 +1,5 @@
<div class="dropdown-widget">
<label [attr.for]="field.id">{{field.name}}</label>
<select [(ngModel)]="field.value" (ngModelChange)="checkVisibility(field)">
<option *ngFor="let opt of field.options" [value]="opt.id">{{opt.name}}</option>
</select>

View File

@ -27,6 +27,7 @@ import { HyperlinkWidget } from './hyperlink/hyperlink.widget';
import { RadioButtonsWidget } from './radio-buttons/radio-buttons.widget';
import { DisplayValueWidget } from './display-value/display-value.widget';
import { DisplayTextWidget } from './display-text/display-text.widget';
import { UploadWidget } from './upload/upload.widget';
// core
export * from './widget.component';
@ -46,6 +47,7 @@ export * from './hyperlink/hyperlink.widget';
export * from './radio-buttons/radio-buttons.widget';
export * from './display-value/display-value.widget';
export * from './display-text/display-text.widget';
export * from './upload/upload.widget';
export const CONTAINER_WIDGET_DIRECTIVES: [any] = [
TabsWidget,
@ -61,7 +63,8 @@ export const PRIMITIVE_WIDGET_DIRECTIVES: [any] = [
HyperlinkWidget,
RadioButtonsWidget,
DisplayValueWidget,
DisplayTextWidget
DisplayTextWidget,
UploadWidget
];

View File

@ -0,0 +1,17 @@
.upload-widget {
width:100%
}
.upload-widget__icon {
float: left;
}
.upload-widget__file {
float: left;
margin-top: 4px;
}
.upload-widget__reset {
float: left;
margin-top: 4px;
}

View File

@ -0,0 +1,15 @@
<div class="upload-widget">
<label [attr.for]="field.id">{{field.name}}</label>
<div>
<i class="material-icons upload-widget__icon">image</i>
<span *ngIf="hasFile" class="upload-widget__file">{{getUploadedFileName()}}</span>
<input *ngIf="!hasFile"
#file
type="file"
[attr.id]="field.id"
class="upload-widget__file"
(change)="onFileChanged($event)">
<button *ngIf="hasFile" (click)="reset(file);" class="upload-widget__reset">X</button>
</div>
</div>

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.
*/
import { Component, OnInit } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'upload-widget',
templateUrl: './upload.widget.html',
styleUrls: ['./upload.widget.css']
})
export class UploadWidget extends WidgetComponent implements OnInit {
constructor(private settingsService: AlfrescoSettingsService,
private authService: AlfrescoAuthenticationService) {
super();
}
hasFile: boolean;
fileName: string;
ngOnInit() {
if (this.field &&
this.field.value &&
this.field.value.length > 0) {
this.hasFile = true;
}
}
getUploadedFileName(): string {
let result = this.fileName;
if (this.field &&
this.field.value &&
this.field.value.length > 0) {
let file = this.field.value[0];
result = file.name;
}
return result;
}
reset() {
this.field.value = null;
this.field.json.value = null;
this.hasFile = false;
}
onFileChanged(event: any) {
let files = event.srcElement.files;
if (files && files.length > 0) {
let file = files[0];
this.hasFile = true;
this.fileName = file.name;
let formData: FormData = new FormData();
formData.append('file', file, file.name);
let xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
let jsonFile = JSON.parse(xhr.response);
console.log(jsonFile);
this.field.value = [jsonFile];
this.field.json.value = [jsonFile];
} else {
console.error(xhr.response);
window.alert('Error uploading file. See console output for more details.');
}
}
};
let url = `${this.settingsService.bpmHost}/activiti-app/app/rest/content/raw`;
xhr.open('POST', url, true);
xhr.setRequestHeader('Authorization', this.authService.getTicketBpm());
xhr.send(formData);
}
}
}

View File

@ -0,0 +1,95 @@
/*!
* @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 class FormDefinitionModel {
reusable: boolean = false;
newVersion: boolean = false;
formRepresentation: any;
formImageBase64: string = '';
constructor(id: string, name: any, lastUpdatedByFullName: string, lastUpdated: string, metadata: any) {
this.formRepresentation = {
id: id,
name: name,
description: '',
version: 1,
lastUpdatedBy: 1,
lastUpdatedByFullName: lastUpdatedByFullName,
lastUpdated: lastUpdated,
stencilSetId: 0,
referenceId: null,
formDefinition: {
fields: [{
name: 'Label',
type: 'container',
fieldType: 'ContainerRepresentation',
numberOfColumns: 2,
required: false,
readOnly: false,
sizeX: 2,
sizeY: 1,
row: -1,
col: -1,
fields: {'1': this.metadataToFields(metadata)}
}],
gridsterForm: false,
javascriptEvents: [],
metadata: {},
outcomes: [],
className: '',
style: '',
tabs: [],
variables: []
}
};
}
private metadataToFields(metadata: any): any[] {
let fields = [];
if (metadata) {
metadata.forEach(function(property) {
if (property) {
let field = {
type: 'text',
id: property.name,
name: property.name,
required: false,
readOnly: false,
sizeX: 1,
sizeY: 1,
row: -1,
col: -1,
colspan: 1,
params: {
existingColspan: 1,
maxColspan: 2
},
layout: {
colspan: 1,
row: -1,
column: -1
}
};
fields.push(field);
}
});
}
return fields;
}
}

View File

@ -0,0 +1,26 @@
/*!
* @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 class NodeMetadata {
metadata: any;
nodeType: string;
constructor(metadata: any, nodeType: string) {
this.metadata = metadata;
this.nodeType = nodeType;
}
}

View File

@ -0,0 +1,262 @@
/*!
* @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 { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { Response, Http, Headers, RequestOptions } from '@angular/http';
import { FormModel } from '../components/widgets/core/form.model';
import { NodeService } from './node.service';
@Injectable()
export class EcmModelService {
public static MODEL_NAMESPACE: string = 'activitiForms';
public static MODEL_NAME: string = 'activitiFormsModel';
public static TYPE_MODEL: string = 'cm:folder';
constructor(private authService: AlfrescoAuthenticationService,
private http: Http,
public alfrescoSettingsService: AlfrescoSettingsService,
private nodeService: NodeService) {
}
public createEcmTypeForActivitiForm(formName: string, form: FormModel): Observable<any> {
return Observable.create(observer => {
this.seachActivitiEcmModel().subscribe(
model => {
if (!model) {
this.createActivitiEcmModel(formName, form).subscribe(typeForm => {
observer.next(typeForm);
observer.complete();
});
} else {
this.createFomType(formName, form).subscribe(typeForm => {
observer.next(typeForm);
observer.complete();
});
}
},
this.handleError
);
});
}
private seachActivitiEcmModel() {
return this.getEcmModels().map(function (ecmModels: any) {
return ecmModels.list.entries.find(model => model.entry.name === EcmModelService.MODEL_NAME);
});
}
private createActivitiEcmModel(formName: string, form: FormModel): Observable<any> {
return Observable.create(observer => {
this.createEcmModel(EcmModelService.MODEL_NAME, EcmModelService.MODEL_NAMESPACE).subscribe(
model => {
console.log('model created', model);
this.activeEcmModel(EcmModelService.MODEL_NAME).subscribe(
modelActive => {
console.log('model active', modelActive);
this.createEcmTypeWithProperties(formName, form).subscribe(typeCreated => {
observer.next(typeCreated);
observer.complete();
});
},
this.handleError
);
},
this.handleError
);
});
}
private createFomType(formName: string, form: FormModel): Observable<any> {
return Observable.create(observer => {
this.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe(
ecmType => {
console.log('custom types', ecmType);
if (!ecmType) {
this.createEcmTypeWithProperties(formName, form).subscribe(typeCreated => {
observer.next(typeCreated);
observer.complete();
});
} else {
observer.next(ecmType);
observer.complete();
}
},
this.handleError
);
});
}
public createEcmTypeWithProperties(formName: string, form: FormModel): Observable<any> {
return Observable.create(observer => {
this.createEcmType(formName, EcmModelService.MODEL_NAME, EcmModelService.TYPE_MODEL).subscribe(
typeCreated => {
console.log('type Created', typeCreated);
this.addPropertyToAType(EcmModelService.MODEL_NAME, formName, form).subscribe(
properyAdded => {
console.log('property Added', properyAdded);
observer.next(typeCreated);
observer.complete();
},
this.handleError);
},
this.handleError);
});
}
public searchEcmType(typeName: string, modelName: string): Observable<any> {
return this.getEcmType(modelName).map(function (customTypes: any) {
return customTypes.list.entries.find(type => type.entry.prefixedName === typeName || type.entry.title === typeName);
});
}
public activeEcmModel(modelName: string): Observable<any> {
let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}?select=status`;
let options = this.getRequestOptions();
let body = {status: 'ACTIVE'};
return this.http
.put(url, body, options)
.map(this.toJson)
.catch(this.handleError);
}
public createEcmModel(modelName: string, nameSpace: string): Observable<any> {
let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm`;
let options = this.getRequestOptions();
let body = {
status: 'DRAFT', namespaceUri: modelName, namespacePrefix: nameSpace, name: modelName, description: '', author: ''
};
return this.http
.post(url, body, options)
.map(this.toJson)
.catch(this.handleError);
}
public getEcmModels(): Observable<any> {
let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm`;
let options = this.getRequestOptions();
return this.http
.get(url, options)
.map(this.toJson)
.catch(this.handleError);
}
public getEcmType(modelName: string): Observable<any> {
let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}/types`;
let options = this.getRequestOptions();
return this.http
.get(url, options)
.map(this.toJson)
.catch(this.handleError);
}
public createEcmType(typeName: string, modelName: string, parentType: string): Observable<any> {
let name = this.cleanNameType(typeName);
let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}/types`;
let options = this.getRequestOptions();
let body = {
name: name,
parentName: parentType,
title: typeName,
description: ''
};
return this.http
.post(url, body, options)
.map(this.toJson)
.catch(this.handleError);
}
public addPropertyToAType(modelName: string, typeName: string, formFields: any) {
let name = this.cleanNameType(typeName);
let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}/types/${name}?select=props`;
let options = this.getRequestOptions();
let properties = [];
if (formFields && formFields.values) {
for (let key in formFields.values) {
if (key) {
properties.push({
name: key,
title: key,
description: key,
dataType: 'd:text',
multiValued: false,
mandatory: false,
mandatoryEnforced: false
});
}
}
}
let body = {
name: name,
properties: properties
};
return this.http
.put(url, body, options)
.map(this.toJson)
.catch(this.handleError);
}
public cleanNameType(name: string): string {
let cleanName = name;
if (name.indexOf(':') !== -1) {
cleanName = name.split(':')[1];
}
return cleanName.replace(/[^a-zA-Z ]/g, '');
}
public getHeaders(): Headers {
return new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': this.authService.getTicketEcmBase64()
});
}
public getRequestOptions(): RequestOptions {
let headers = this.getHeaders();
return new RequestOptions({headers: headers});
}
toJson(res: Response) {
if (res) {
let body = res.json();
return body || {};
}
return {};
}
handleError(err: any): any {
console.log(err);
}
}

View File

@ -17,8 +17,10 @@
import { it, inject, describe, expect, beforeEach, beforeEachProviders, afterEach } from '@angular/core/testing';
import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core';
import { Response, ResponseOptions } from '@angular/http';
import { HTTP_PROVIDERS, Response, ResponseOptions } from '@angular/http';
import { FormService } from './form.service';
import { EcmModelService } from './ecm-model.service';
import { NodeService } from './node.service';
declare let jasmine: any;
@ -30,7 +32,10 @@ describe('FormService', () => {
return [
FormService,
AlfrescoSettingsService,
AlfrescoAuthenticationService
AlfrescoAuthenticationService,
EcmModelService,
HTTP_PROVIDERS,
NodeService
];
});

View File

@ -15,10 +15,12 @@
* limitations under the License.
*/
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Rx';
import {AlfrescoAuthenticationService} from 'ng2-alfresco-core';
import {FormValues} from './../components/widgets/core/index';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { FormValues } from './../components/widgets/core/index';
import { FormDefinitionModel } from '../models/form-definition.model';
import { EcmModelService } from './ecm-model.service';
@Injectable()
export class FormService {
@ -26,28 +28,127 @@ export class FormService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
constructor(private authService: AlfrescoAuthenticationService) {
constructor(private authService: AlfrescoAuthenticationService,
private ecmModelService: EcmModelService) {
}
getProcessDefinitions(): Observable<any> {
/**
* Create a Form with a fields for each metadata properties
* @returns {Observable<any>}
*/
public createFormFromNodeType(formName: string): Observable<any> {
return Observable.create(observer => {
this.createForm(formName).subscribe(
form => {
this.ecmModelService.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe(
customType => {
let formDefinitionModel = new FormDefinitionModel(form.id, form.name, form.lastUpdatedByFullName, form.lastUpdated, customType.entry.properties);
this.addFieldsNodeTypePropertiesToTheForm(form.id, formDefinitionModel).subscribe(formData => {
observer.next(formData);
observer.complete();
}, this.handleError);
},
this.handleError);
}, this.handleError);
});
}
/**
* Create a Form
* @returns {Observable<any>}
*/
public createForm(formName: string): Observable<any> {
let dataModel = {
name: formName,
description: '',
modelType: 2,
stencilSet: 0
};
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.createModel(dataModel));
}
/**
* Add Fields to A form from a metadata properties
* @returns {Observable<any>}
*/
public addFieldsNodeTypePropertiesToTheForm(formId: string, formDefinitionModel: FormDefinitionModel): Observable<any> {
return this.addFieldsToAForm(formId, formDefinitionModel);
}
/**
* Add Fileds to A form
* @returns {Observable<any>}
*/
public addFieldsToAForm(formId: string, formModel: FormDefinitionModel): Observable<any> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.editorApi.saveForm(formId, formModel));
}
/**
* Search For A Form by name
* @returns {Observable<any>}
*/
public searchFrom(name: string): Observable<any> {
let opts = {
'modelType': 2
};
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)).map(function (forms: any) {
return forms.data.find(formdata => formdata.name === name);
}).catch(this.handleError);
}
/**
* Get All the forms
* @returns {Observable<any>}
*/
public getForms(): Observable<any> {
let opts = {
'modelType': 2
};
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts));
}
/**
* Get Process Definition
* @returns {Observable<any>}
*/
public getProcessDefinitions(): Observable<any> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessDefinitions({}))
.map(this.toJsonArray)
.catch(this.handleError);
}
getTasks(): Observable<any> {
/**
* Get All the Tasks
* @param taskId Task Id
* @returns {Observable<any>}
*/
public getTasks(): Observable<any> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.listTasks({}))
.map(this.toJsonArray)
.catch(this.handleError);
}
getTask(taskId: string): Observable<any> {
/**
* Get Task
* @param taskId Task Id
* @returns {Observable<any>}
*/
public getTask(taskId: string): Observable<any> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTask(taskId))
.map(this.toJson)
.catch(this.handleError);
}
saveTaskForm(taskId: string, formValues: FormValues): Observable<any> {
/**
* Save Task Form
* @param taskId Task Id
* @param formValues Form Values
* @returns {Observable<any>}
*/
public saveTaskForm(taskId: string, formValues: FormValues): Observable<any> {
let body = JSON.stringify({values: formValues});
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.saveTaskForm(taskId, body))
@ -59,9 +160,9 @@ export class FormService {
* @param taskId Task Id
* @param formValues Form Values
* @param outcome Form Outcome
* @returns {any}
* @returns {Observable<any>}
*/
completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> {
public completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> {
let data: any = {values: formValues};
if (outcome) {
data.outcome = outcome;
@ -72,13 +173,23 @@ export class FormService {
.catch(this.handleError);
}
getTaskForm(taskId: string): Observable<any> {
/**
* Get Form related to a taskId
* @param taskId Task Id
* @returns {Observable<any>}
*/
public getTaskForm(taskId: string): Observable<any> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTaskForm(taskId))
.map(this.toJson)
.catch(this.handleError);
}
getFormDefinitionById(formId: string): Observable<any> {
/**
* Get Form Definition
* @param formId Form Id
* @returns {Observable<any>}
*/
public getFormDefinitionById(formId: string): Observable<any> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.editorApi.getForm(formId))
.map(this.toJson)
.catch(this.handleError);
@ -89,7 +200,7 @@ export class FormService {
* @param name
* @returns {Promise<T>|Promise<ErrorObservable>}
*/
getFormDefinitionByName(name: string): Observable<any> {
public getFormDefinitionByName(name: string): Observable<any> {
let opts = {
'filter': 'myReusableForms',
'filterText': name,

View File

@ -0,0 +1,96 @@
/*!
* @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 { AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { NodeMetadata } from '../models/node-metadata.model';
@Injectable()
export class NodeService {
constructor(private authService: AlfrescoAuthenticationService) {
}
/**
* Get All the metadata and the nodeType for a nodeId cleaned by the prefix
* @param nodeId Node Id
* @returns NodeMetadata
*/
public getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
return Observable.fromPromise(this.authService.getAlfrescoApi().nodes.getNodeInfo(nodeId)).map(this.cleanMetadataFromSemicolon);
}
/**
* Create a new Node from form metadata
* @param path path
* @param nodeType node type
* @param nameSpace namespace node
* @param data data to store
* @returns NodeMetadata
*/
public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable<any> {
let properties = {};
for (let key in data) {
if (data[key]) {
properties[nameSpace + ':' + key] = data[key];
}
}
return this.createNode(name || this.generateUuid(), nodeType, properties, path);
}
/**
* Create a new Node from form metadata
* @param name path
* @param nodeType node type
* @param properties namespace node
* @param path path
* @returns NodeMetadata
*/
public createNode(name: string, nodeType: string, properties: any, path: string): Observable<any> {
let body = {
name: name,
nodeType: nodeType,
properties: properties,
relativePath: path
};
return Observable.fromPromise(this.authService.getAlfrescoApi().nodes.addNode('-root-', body, {}));
}
private generateUuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
private cleanMetadataFromSemicolon(data: any): NodeMetadata {
let metadata = {};
if (data && data.properties) {
for (let key in data.properties) {
if (key) {
metadata [key.split(':')[1]] = data.properties[key];
}
}
}
return new NodeMetadata(metadata, data.nodeType);
}
}

View File

@ -236,6 +236,7 @@ describe('WidgetVisibilityService', () => {
expect(varValue).toBe('form_value_test');
});
/*
it('should return null if the variable does not exist', (done) => {
service.getTaskProcessVariableModelsForTask(9999).subscribe(
(res: TaskProcessVariableModel[]) => {
@ -252,6 +253,7 @@ describe('WidgetVisibilityService', () => {
expect(varValue).toBe(null);
});
*/
it('should be able to retrieve a field value searching in the form', () => {
let stubFormWithFields = new FormModel(fakeFormJson);
@ -360,6 +362,7 @@ describe('WidgetVisibilityService', () => {
expect(rightValue).toBe(null);
});
/*
it('should retrieve the value for the left field when it is a process variable', (variableUpdated) => {
service.getTaskProcessVariableModelsForTask(9999).subscribe(
(res: TaskProcessVariableModel[]) => {
@ -379,6 +382,7 @@ describe('WidgetVisibilityService', () => {
expect(rightValue).not.toBe(null);
expect(rightValue).toBe('test_value_2');
});
*/
it('should retrieve the value for the left field when it is a form variable', () => {
let fakeForm = new FormModel({variables: [
@ -484,6 +488,7 @@ describe('WidgetVisibilityService', () => {
expect(isVisible).toBeTruthy();
});
/*
it('should evaluate visibility with multiple conditions', (ready) => {
service.getTaskProcessVariableModelsForTask(9999).subscribe(
(res: TaskProcessVariableModel[]) => {
@ -510,6 +515,7 @@ describe('WidgetVisibilityService', () => {
expect(isVisible).toBeTruthy();
});
*/
it('should return true when the visibility condition is not valid', () => {
let visibilityObjTest = new WidgetVisibilityModel();

View File

@ -10,4 +10,6 @@ src/**/*.d.ts
demo/**/*.js
demo/**/*.js.map
demo/**/*.d.ts
index.js
index.js.map
!systemjs.config.js

View File

@ -0,0 +1,123 @@
/*!
* @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 { DatePipe } from '@angular/common';
import {
DataTableAdapter, ObjectDataTableAdapter, ObjectDataColumn,
DataRow, DataColumn, DataSorting
} from 'ng2-alfresco-datatable';
export class ProcessListDataTableAdapter extends ObjectDataTableAdapter implements DataTableAdapter {
ERR_ROW_NOT_FOUND: string = 'Row not found';
ERR_COL_NOT_FOUND: string = 'Column not found';
DEFAULT_DATE_FORMAT: string = 'medium';
private sorting: DataSorting;
private rows: DataRow[];
private columns: DataColumn[];
constructor(rows: any, schema: DataColumn[]) {
super(rows, schema);
this.rows = rows;
this.columns = schema || [];
}
getRows(): Array<DataRow> {
return this.rows;
}
// TODO: disable this api
setRows(rows: Array<DataRow>) {
this.rows = rows || [];
this.sort();
}
getColumns(): Array<DataColumn> {
return this.columns;
}
setColumns(columns: Array<DataColumn>) {
this.columns = columns || [];
}
getValue(row: DataRow, col: DataColumn): any {
if (!row) {
throw new Error(this.ERR_ROW_NOT_FOUND);
}
if (!col) {
throw new Error(this.ERR_COL_NOT_FOUND);
}
let value = row.getValue(col.key);
if (col.type === 'date') {
let datePipe = new DatePipe();
let format = (<ActivitiDataColumn>(col)).format || this.DEFAULT_DATE_FORMAT;
try {
return datePipe.transform(value, format);
} catch (err) {
console.error(`Error parsing date ${value} to format ${format}`);
}
}
return value;
}
getSorting(): DataSorting {
return this.sorting;
}
setSorting(sorting: DataSorting): void {
this.sorting = sorting;
if (sorting && sorting.key && this.rows && this.rows.length > 0) {
this.rows.sort((a: DataRow, b: DataRow) => {
let left = a.getValue(sorting.key);
if (left) {
left = (left instanceof Date) ? left.valueOf().toString() : left.toString();
} else {
left = '';
}
let right = b.getValue(sorting.key);
if (right) {
right = (right instanceof Date) ? right.valueOf().toString() : right.toString();
} else {
right = '';
}
return sorting.direction === 'asc'
? left.localeCompare(right)
: right.localeCompare(left);
});
}
}
sort(key?: string, direction?: string): void {
let sorting = this.sorting || new DataSorting();
if (key) {
sorting.key = key;
sorting.direction = direction || 'asc';
}
this.setSorting(sorting);
}
}
export class ActivitiDataColumn extends ObjectDataColumn {
format: string;
}

View File

@ -39,7 +39,7 @@
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.6",
"zone.js": "^0.6.12",
"ng2-activiti-processlist": "file:../",
"ng2-activiti-processlist": "^0.3.0",
"material-design-icons": "^2.2.3",
"material-design-lite": "^1.1.3"
},

View File

@ -44,7 +44,7 @@ import {
<label for="token"><b>Insert a servicePath</b></label><br>
<input id="token" type="text" size="48" [(ngModel)]="servicePath"><br>
<div class="container" *ngIf="authenticated">
<activiti-processlist></activiti-processlist>
<activiti-process-instance-list></activiti-process-instance-list>
</div>`,
providers: [ACTIVITI_PROCESSLIST_PROVIDERS],
directives: [ACTIVITI_PROCESSLIST_DIRECTIVES]

View File

@ -15,17 +15,30 @@
* limitations under the License.
*/
import { ActivitiProcesslistComponent } from './src/components/activiti-processlist.component';
import { ActivitiProcessInstanceListComponent } from './src/components/activiti-processlist.component';
import { ActivitiProcessFilters } from './src/components/activiti-filters.component';
import { ActivitiProcessInstanceHeader } from './src/components/activiti-process-instance-header.component';
import { ActivitiProcessInstanceTasks } from './src/components/activiti-process-instance-tasks.component';
import { ActivitiComments } from './src/components/activiti-comments.component';
import { ActivitiProcessInstanceDetails } from './src/components/activiti-process-instance-details.component';
import { ActivitiStartProcessButton } from './src/components/activiti-start-process.component';
import { ActivitiProcessService } from './src/services/activiti-process.service';
// components
export * from './src/components/activiti-processlist.component';
export * from './src/components/activiti-process-instance-details.component';
// services
export * from './src/services/activiti-process.service';
export const ACTIVITI_PROCESSLIST_DIRECTIVES: [any] = [
ActivitiProcesslistComponent
ActivitiProcessInstanceListComponent,
ActivitiProcessFilters,
ActivitiProcessInstanceDetails,
ActivitiProcessInstanceHeader,
ActivitiProcessInstanceTasks,
ActivitiComments,
ActivitiStartProcessButton
];
export const ACTIVITI_PROCESSLIST_PROVIDERS: [any] = [

View File

@ -20,6 +20,9 @@ module.exports = function (config) {
{pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.html', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.css', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.html', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.css', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', included: true, watched: false},

View File

@ -1,7 +1,7 @@
{
"name": "ng2-activiti-processlist",
"description": "Show active processes from the Activiti BPM suite",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
@ -63,8 +63,9 @@
"rxjs": "5.0.0-beta.6",
"zone.js": "^0.6.12",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0"
"ng2-alfresco-core": "0.3.0",
"ng2-alfresco-datatable": "0.3.0",
"ng2-activiti-tasklist": "0.3.0"
},
"devDependencies": {
"angular-cli": "1.0.0-beta.9",

View File

@ -0,0 +1,7 @@
:host {
width: 100%;
}
.activiti-label {
font-weight: bolder;
}

View File

@ -0,0 +1,35 @@
<span class="activiti-label mdl-badge"
[attr.data-badge]="comments?.length">{{ 'DETAILS.LABELS.COMMENTS' |translate }}</span>
<div id="addComment" (click)="showDialog()" class="icon material-icons">add</div>
<div class="mdl-tooltip" for="addComment">
Add a comment
</div>
<div class="menu-container" *ngIf="comments?.length > 0">
<ul class='mdl-list'>
<li class="mdl-list__item" *ngFor="let comment of comments">
<span class="mdl-list__item-primary-content">
<i class="material-icons mdl-list__item-icon">comment</i>
{{comment.message}}
</span>
</li>
</ul>
</div>
<div *ngIf="comments?.length === 0">
{{ 'DETAILS.COMMENTS.NONE' | translate }}
</div>
<dialog class="mdl-dialog" #dialog>
<h4 class="mdl-dialog__title">New comment</h4>
<div class="mdl-dialog__content">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<textarea class="mdl-textfield__input" type="text" [(ngModel)]="message" rows="1" id="commentText"></textarea>
<label class="mdl-textfield__label" for="commentText">Message</label>
</div>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="add()" class="mdl-button">Add Comment</button>
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
</div>
</dialog>

View File

@ -0,0 +1,121 @@
/*!
* @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, ViewChild } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { Comment } from '../models/comment.model';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
declare let componentHandler: any;
declare let __moduleName: string;
@Component({
selector: 'activiti-comments',
moduleId: __moduleName,
templateUrl: './activiti-comments.component.html',
styleUrls: ['./activiti-comments.component.css'],
providers: [ActivitiProcessService],
pipes: [ AlfrescoPipeTranslate ]
})
export class ActivitiComments implements OnInit {
@Input()
processId: string;
@ViewChild('dialog')
dialog: any;
comments: Comment [] = [];
private commentObserver: Observer<Comment>;
comment$: Observable<Comment>;
message: string;
/**
* Constructor
* @param auth
* @param translate
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
private activitiProcess: ActivitiProcessService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src');
}
this.comment$ = new Observable<Comment>(observer => this.commentObserver = observer).share();
}
ngOnInit() {
this.comment$.subscribe((comment: Comment) => {
this.comments.push(comment);
});
if (this.processId) {
this.load(this.processId);
}
}
public load(taskId: string) {
this.comments = [];
if (this.processId) {
this.activitiProcess.getProcessInstanceComments(this.processId).subscribe(
(res: Comment[]) => {
res.forEach((comment) => {
this.commentObserver.next(comment);
});
},
(err) => {
console.log(err);
}
);
} else {
this.comments = [];
}
}
public showDialog() {
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
public add() {
this.activitiProcess.addProcessInstanceComment(this.processId, this.message).subscribe(
(res: Comment) => {
this.comments.push(res);
this.message = '';
},
(err) => {
console.log(err);
}
);
this.cancel();
}
public cancel() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
}
}

View File

@ -0,0 +1,3 @@
.mdl-list__item {
cursor: pointer;
}

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,131 @@
/*!
* @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, Input } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.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-process-filters',
moduleId: __moduleName,
templateUrl: './activiti-filters.component.html',
styleUrls: ['activiti-filters.component.css'],
providers: [ActivitiProcessService],
pipes: [AlfrescoPipeTranslate]
})
export class ActivitiProcessFilters implements OnInit {
@Output()
filterClick: EventEmitter<FilterModel> = new EventEmitter<FilterModel>();
@Output()
onSuccess: EventEmitter<any> = new EventEmitter<any>();
@Output()
onError: EventEmitter<any> = new EventEmitter<any>();
@Input()
appId: string;
@Input()
appName: string;
private filterObserver: Observer<FilterModel>;
filter$: Observable<FilterModel>;
currentFilter: FilterModel;
filters: FilterModel [] = [];
/**
* Constructor
* @param auth
* @param translate
* @param activiti
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
public activiti: ActivitiProcessService) {
this.filter$ = new Observable<FilterModel>(observer => this.filterObserver = observer).share();
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src');
}
}
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() {
if (this.appName) {
this.filterByAppName();
} else {
this.filterByAppId(this.appId);
}
}
private filterByAppId(appId) {
this.activiti.getProcessFilters(appId).subscribe(
(res: FilterModel[]) => {
res.forEach((filter) => {
this.filterObserver.next(filter);
});
this.onSuccess.emit(res);
},
(err) => {
console.log(err);
this.onError.emit(err);
}
);
}
private filterByAppName() {
this.activiti.getDeployedApplications(this.appName).subscribe(
application => {
this.filterByAppId(application.id);
},
(err) => {
console.log(err);
this.onError.emit(err);
});
}
/**
* Pass the selected filter as next
* @param filter
*/
public selectFilter(filter: FilterModel) {
this.currentFilter = filter;
this.filterClick.emit(filter);
}
}

View File

@ -0,0 +1,13 @@
<div *ngIf="!processInstanceDetails">{{ 'DETAILS.MESSAGES.NONE'|translate }}</div>
<div *ngIf="processInstanceDetails">
<h2 class="mdl-card__title-text">{{processInstanceDetails.name}}</h2>
<activiti-process-instance-header [processInstance]="processInstanceDetails" (processCancelled)="processCancelled()" #activitiprocessheader></activiti-process-instance-header>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--8-col">
<activiti-process-instance-tasks [processId]="processInstanceDetails.id" (taskFormCompleted)="taskFormCompleted()" #activitiprocesstasks></activiti-process-instance-tasks>
</div>
<div class="mdl-cell mdl-cell--4-col">
<activiti-comments [processId]="processInstanceDetails.id" #activitiprocesscomments></activiti-comments>
</div>
</div>
</div>

View File

@ -0,0 +1,108 @@
/*!
* @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, ViewChild, Output, EventEmitter } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { ActivitiProcessInstanceHeader } from './activiti-process-instance-header.component';
import { ActivitiProcessInstanceTasks } from './activiti-process-instance-tasks.component';
import { ActivitiComments } from './activiti-comments.component';
import { ProcessInstance } from '../models/process-instance';
declare let componentHandler: any;
declare let __moduleName: string;
@Component({
selector: 'activiti-process-instance-details',
moduleId: __moduleName,
templateUrl: './activiti-process-instance-details.component.html',
styleUrls: ['./activiti-process-instance-details.component.css'],
providers: [ActivitiProcessService],
directives: [ActivitiProcessInstanceHeader, ActivitiComments, ActivitiProcessInstanceTasks],
pipes: [AlfrescoPipeTranslate]
})
export class ActivitiProcessInstanceDetails {
@Input()
processInstanceId: string;
@ViewChild('activitiprocessheader')
processInstanceHeader: ActivitiProcessInstanceHeader;
@ViewChild('activitiprocesstasks')
tasksList: ActivitiProcessInstanceTasks;
@ViewChild('activitiprocesscomments')
commentsList: ActivitiComments;
@Input()
showTitle: boolean = true;
@Input()
showRefreshButton: boolean = true;
@Output()
processCancelledEmitter = new EventEmitter();
@Output()
taskFormCompletedEmitter = new EventEmitter();
processInstanceDetails: ProcessInstance;
/**
* Constructor
* @param auth
* @param translate
* @param activitiProcess
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
private activitiProcess: ActivitiProcessService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src');
}
}
load(processId: string) {
if (processId) {
this.activitiProcess.getProcess(processId).subscribe(
(res: ProcessInstance) => {
this.processInstanceDetails = res;
if (this.processInstanceDetails) {
if (this.commentsList) {
this.commentsList.load(this.processInstanceDetails.id);
}
if (this.tasksList) {
this.tasksList.load(this.processInstanceDetails.id);
}
}
}
);
}
}
processCancelled(data: any) {
this.processCancelledEmitter.emit(data);
}
taskFormCompleted(data: any) {
this.taskFormCompletedEmitter.emit(data);
}
}

View File

@ -0,0 +1,7 @@
:host {
width: 100%;
}
.activiti-label {
font-weight: bolder;
}

View File

@ -0,0 +1,15 @@
<div *ngIf="processInstance">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--4-col">
<span class="activiti-label">{{ 'DETAILS.LABELS.STARTED_BY' | translate }}</span>:
{{getStartedByFullName()}}
</div>
<div class="mdl-cell mdl-cell--4-col">
<span class="activiti-label">{{ 'DETAILS.LABELS.STARTED' | translate }}</span>:
{{getStartedDate() | date:'medium'}}
</div>
<div class="mdl-cell mdl-cell--4-col">
<button type="button" (click)="cancelProcess()" class="mdl-button">{{ 'DETAILS.BUTTON.CANCEL' | translate }}</button>
</div>
</div>
</div>

View File

@ -0,0 +1,73 @@
/*!
* @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, Output, EventEmitter } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ProcessInstance } from '../models/process-instance';
import { ActivitiProcessService } from './../services/activiti-process.service';
declare let componentHandler: any;
declare let __moduleName: string;
@Component({
selector: 'activiti-process-instance-header',
moduleId: __moduleName,
templateUrl: './activiti-process-instance-header.component.html',
styleUrls: ['./activiti-process-instance-header.component.css'],
pipes: [ AlfrescoPipeTranslate ]
})
export class ActivitiProcessInstanceHeader {
@Input()
processInstance: ProcessInstance;
@Output()
processCancelled = new EventEmitter();
/**
* Constructor
* @param auth
* @param translate
* @param activitiProcess
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
private activitiProcess: ActivitiProcessService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
}
}
getStartedByFullName() {
if (this.processInstance && this.processInstance.startedBy) {
return (this.processInstance.startedBy.firstName && this.processInstance.startedBy.firstName !== 'null'
? this.processInstance.startedBy.firstName + ' ' : '') +
this.processInstance.startedBy.lastName;
}
return '';
}
getStartedDate() {
return this.processInstance ? new Date(this.processInstance.started) : null;
}
cancelProcess() {
this.processCancelled.emit(this.activitiProcess.cancelProcess(this.processInstance.id));
}
}

View File

@ -0,0 +1,19 @@
:host {
width: 100%;
}
.activiti-label {
font-weight: bolder;
}
.material-icons:hover {
color: rgb(255, 152, 0);
}
.task-details-dialog {
width: 600px;
}
.process-tasks-refresh {
float: right;
}

View File

@ -0,0 +1,52 @@
<div *ngIf="showRefreshButton" class="process-tasks-refresh" >
<button (click)="onRefreshClicked()"
class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
<i class="material-icons">refresh</i>
</button>
</div>
<span class="activiti-label mdl-badge"
[attr.data-badge]="activeTasks?.length">{{ 'DETAILS.LABELS.TASKS_ACTIVE'|translate }}</span>
<div class="menu-container" *ngIf="activeTasks?.length > 0">
<ul class='mdl-list'>
<li class="mdl-list__item mdl-list__item--two-line" *ngFor="let task of activeTasks">
<span class="mdl-list__item-primary-content" (click)="clickTask($event, task)">
<i class="material-icons mdl-list__item-icon">assignment</i>
<span>{{task.name}}</span>
<span class="mdl-list__item-sub-title">{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: getUserFullName(task.assignee), created: task.created | date:'mediumDate' } }}</span>
</span>
</li>
</ul>
</div>
<div *ngIf="activeTasks?.length === 0">
{{ 'DETAILS.TASKS.NO_ACTIVE' | translate }}
</div>
<span class="activiti-label mdl-badge"
[attr.data-badge]="completedTasks?.length">{{ 'DETAILS.LABELS.TASKS_COMPLETED'|translate }}</span>
<div class="menu-container" *ngIf="completedTasks?.length > 0">
<ul class='mdl-list'>
<li class="mdl-list__item mdl-list__item--two-line" *ngFor="let task of completedTasks">
<span class="mdl-list__item-primary-content" (click)="clickTask($event, task)">
<i class="material-icons mdl-list__item-icon">assignment</i>
<span>{{task.name}}</span>
<span class="mdl-list__item-sub-title">{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: getUserFullName(task.assignee), created: task.created | date:'mediumDate' } }}</span>
</span>
</li>
</ul>
</div>
<div *ngIf="completedTasks?.length === 0">
{{ 'DETAILS.TASKS.NO_COMPLETED' | translate }}
</div>
<dialog class="mdl-dialog task-details-dialog" #dialog>
<h4 class="mdl-dialog__title">{{ 'DETAILS.TASKS.TASK_DETAILS' | translate }}</h4>
<div class="mdl-dialog__content">
<activiti-task-details [taskId]="selectedTaskId" (formCompleted)="taskFormCompleted()" #taskdetails></activiti-task-details>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="cancelDialog()" class="mdl-button close">{{ 'DETAILS.TASKS.TASK_CLOSE' | translate }}</button>
</div>
</dialog>

View File

@ -0,0 +1,182 @@
/*!
* @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, ViewChild, Output, EventEmitter } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { TaskDetailsModel } from '../models/task-details.model';
import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
declare let componentHandler: any;
declare let __moduleName: string;
@Component({
selector: 'activiti-process-instance-tasks',
moduleId: __moduleName,
templateUrl: './activiti-process-instance-tasks.component.html',
styleUrls: ['./activiti-process-instance-tasks.component.css'],
providers: [ActivitiProcessService],
directives: [ ALFRESCO_TASKLIST_DIRECTIVES ],
pipes: [ AlfrescoPipeTranslate ]
})
export class ActivitiProcessInstanceTasks implements OnInit {
@Input()
processId: string;
@Input()
showRefreshButton: boolean = true;
@Output()
taskFormCompletedEmitter = new EventEmitter();
activeTasks: TaskDetailsModel[] = [];
completedTasks: TaskDetailsModel[] = [];
private taskObserver: Observer<TaskDetailsModel>;
private completedTaskObserver: Observer<TaskDetailsModel>;
task$: Observable<TaskDetailsModel>;
completedTask$: Observable<TaskDetailsModel>;
message: string;
selectedTaskId: string;
@ViewChild('dialog')
dialog: any;
@ViewChild('taskdetails')
taskdetails: any;
/**
* Constructor
* @param auth
* @param translate
* @param activitiProcess
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
private activitiProcess: ActivitiProcessService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src');
}
this.task$ = new Observable<TaskDetailsModel>(observer => this.taskObserver = observer).share();
this.completedTask$ = new Observable<TaskDetailsModel>(observer => this.completedTaskObserver = observer).share();
}
ngOnInit() {
this.task$.subscribe((task: TaskDetailsModel) => {
this.activeTasks.push(task);
});
this.completedTask$.subscribe((task: TaskDetailsModel) => {
this.completedTasks.push(task);
});
if (this.processId) {
this.load(this.processId);
}
}
public load(processId: string) {
this.loadActive(processId);
this.loadCompleted(processId);
}
public loadActive(processId: string) {
this.activeTasks = [];
if (processId) {
this.activitiProcess.getProcessTasks(processId, null).subscribe(
(res: TaskDetailsModel[]) => {
res.forEach((task) => {
this.taskObserver.next(task);
});
},
(err) => {
console.log(err);
}
);
} else {
this.activeTasks = [];
}
}
public loadCompleted(processId: string) {
this.completedTasks = [];
if (processId) {
this.activitiProcess.getProcessTasks(processId, 'completed').subscribe(
(res: TaskDetailsModel[]) => {
res.forEach((task) => {
this.completedTaskObserver.next(task);
});
},
(err) => {
console.log(err);
}
);
} else {
this.completedTasks = [];
}
}
getUserFullName(user: any) {
if (user) {
return (user.firstName && user.firstName !== 'null'
? user.firstName + ' ' : '') +
user.lastName;
}
return '';
}
public clickTask($event: any, task: TaskDetailsModel) {
this.selectedTaskId = task.id;
this.taskdetails.loadDetails(task.id);
this.showDialog();
}
public showDialog() {
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
public cancelDialog() {
this.closeDialog();
}
private closeDialog() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
}
public taskFormCompleted() {
this.closeDialog();
this.load(this.processId);
this.taskFormCompletedEmitter.emit(this.processId);
}
public onRefreshClicked() {
this.load(this.processId);
}
}

View File

@ -1,6 +1,12 @@
<h1>My Activiti Processes</h1>
<p *ngIf="processInstances && processInstances.length == 0">{{ 'PROCESSLIST.NONE' | translate }}</p>
<alfresco-datatable
[data]="data">
</alfresco-datatable>
<div *ngIf="!filter">{{ 'FILTERS.MESSAGES.NONE' | translate }}</div>
<div *ngIf="filter">
<div *ngIf="!isListEmpty()">
<alfresco-datatable
[data]="data"
(rowClick)="onRowClick($event)">
</alfresco-datatable>
</div>
<div *ngIf="isListEmpty()">
{{ 'PROCESSLIST.NONE' | translate }}
</div>
</div>

View File

@ -1,65 +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 {describe, expect, it, inject, beforeEachProviders, beforeEach} from '@angular/core/testing';
import {TestComponentBuilder} from '@angular/compiler/testing';
import {AlfrescoSettingsService, AlfrescoTranslationService, AlfrescoAuthenticationService} from 'ng2-alfresco-core';
import {ActivitiProcesslistComponent} from '../../src/components/activiti-processlist.component';
import {TranslationMock} from './../assets/translation.service.mock';
import {ActivitiProcessService} from '../services/activiti-process.service';
describe('ActivitiProcesslistComponent', () => {
let processlistComponentFixture, element, component;
beforeEachProviders(() => {
return [
ActivitiProcessService,
AlfrescoSettingsService,
AlfrescoAuthenticationService,
{provide: AlfrescoTranslationService, useClass: TranslationMock}
];
});
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb
.createAsync(ActivitiProcesslistComponent)
.then(fixture => {
processlistComponentFixture = fixture;
element = processlistComponentFixture.nativeElement;
component = processlistComponentFixture.componentInstance;
});
}));
it('should have a valid title', () => {
expect(element.querySelector('h1')).toBeDefined();
expect(element.getElementsByTagName('h1')[0].innerHTML).toEqual('My Activiti Processes');
});
it('should contain a list of processes', () => {
let componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
window['componentHandler'] = componentHandler;
component.ngOnInit();
processlistComponentFixture.detectChanges();
expect(element.querySelector('table')).toBeDefined();
expect(element.querySelectorAll('table tbody tr').length).toEqual(1);
});
});

View File

@ -15,17 +15,17 @@
* limitations under the License.
*/
import {Component, OnInit } from '@angular/core';
import {Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { AlfrescoPipeTranslate, AlfrescoTranslationService, CONTEXT_MENU_DIRECTIVES, CONTEXT_MENU_PROVIDERS } from 'ng2-alfresco-core';
import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter, DataRowEvent } from 'ng2-alfresco-datatable';
import { ActivitiProcessService } from '../services/activiti-process.service';
import { ProcessInstance } from '../models/process-instance';
import { FilterModel } from '../models/filter.model';
declare let __moduleName: string;
@Component({
moduleId: __moduleName,
selector: 'activiti-processlist',
selector: 'activiti-process-instance-list',
styles: [
`
:host h1 {
@ -36,13 +36,33 @@ declare let __moduleName: string;
templateUrl: './activiti-processlist.component.html',
directives: [ ALFRESCO_DATATABLE_DIRECTIVES, CONTEXT_MENU_DIRECTIVES ],
pipes: [ AlfrescoPipeTranslate ],
providers: [ CONTEXT_MENU_PROVIDERS ]
providers: [ CONTEXT_MENU_PROVIDERS, ActivitiProcessService ]
})
export class ActivitiProcesslistComponent implements OnInit {
export class ActivitiProcessInstanceListComponent implements OnInit {
errorMessage: string;
processInstances: ProcessInstance[];
data: ObjectDataTableAdapter;
currentProcessInstanceId: string;
@Input()
filter: FilterModel;
@Input()
schemaColumn: any[] = [
{type: 'text', key: 'id', title: 'Id', sortable: true},
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true},
{type: 'text', key: 'started', title: 'Started', sortable: true},
{type: 'text', key: 'startedBy.email', title: 'Started By', sortable: true}
];
@Output()
rowClick: EventEmitter<string> = new EventEmitter<string>();
@Output()
onSuccess: EventEmitter<any> = new EventEmitter<any>();
@Output()
onError: EventEmitter<any> = new EventEmitter<any>();
constructor (private processService: ActivitiProcessService, private translate: AlfrescoTranslationService) {
if (translate !== null) {
@ -51,27 +71,77 @@ export class ActivitiProcesslistComponent implements OnInit {
}
ngOnInit() {
this.getProcesses();
this.data = new ObjectDataTableAdapter(
[],
this.schemaColumn
);
if (this.filter) {
this.load(this.filter);
}
}
getProcesses() {
this.processService.getProcesses()
load(filter: FilterModel) {
this.processService.getProcessInstances(filter)
.subscribe(
(processInstances) => {
this.data = new ObjectDataTableAdapter(
processInstances,
[
{type: 'text', key: 'id', title: 'Id', sortable: true},
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true},
{type: 'text', key: 'started', title: 'Started', sortable: true},
{type: 'text', key: 'startedBy.email', title: 'Started By', sortable: true}
]
);
this.renderProcessInstances(processInstances);
this.onSuccess.emit(processInstances);
},
error => this.errorMessage = <any>error);
error => {
this.errorMessage = <any>error;
this.onError.emit(error);
});
}
onItemClick(processInstance: ProcessInstance, event: any) {
console.log(processInstance, event);
reload() {
this.load(this.filter);
}
/**
* Render the process list
*
* @param processInstances
*/
private renderProcessInstances(processInstances: any[]) {
processInstances = this.optimizeProcessNames(processInstances);
this.data = new ObjectDataTableAdapter(
processInstances,
this.schemaColumn
);
}
/**
* Check if the list is empty
* @returns {ObjectDataTableAdapter|boolean}
*/
isListEmpty(): boolean {
return this.data === undefined ||
(this.data && this.data.getRows() && this.data.getRows().length === 0);
}
/**
* Emit the event rowClick passing the current task id when the row is clicked
* @param event
*/
onRowClick(event: DataRowEvent) {
let item = event;
this.currentProcessInstanceId = item.value.getValue('id');
this.rowClick.emit(this.currentProcessInstanceId);
}
/**
* Optimize process name field
* @param tasks
* @returns {any[]}
*/
private optimizeProcessNames(tasks: any[]) {
tasks = tasks.map(t => {
t.name = t.name || 'No name';
if (t.name.length > 50) {
t.name = t.name.substring(0, 50) + '...';
}
return t;
});
return tasks;
}
}

View File

@ -0,0 +1,11 @@
:host {
width: 100%;
}
.activiti-label {
font-weight: bolder;
}
.material-icons:hover {
color: rgb(255, 152, 0);
}

View File

@ -0,0 +1,23 @@
<button type="button" (click)="showDialog()" class="mdl-button">{{'START_PROCESS.BUTTON'|translate}}</button>
<dialog class="mdl-dialog" #dialog>
<h4 class="mdl-dialog__title">{{'START_PROCESS.DIALOG.TITLE'|translate}}</h4>
<div class="mdl-dialog__content">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<select name="processDefinition" [(ngModel)]="processDefinition" id="processDefinition">
<option *ngFor="let processDef of processDefinitions" [value]="processDef.id">
{{processDef.name}}
</option>
</select>
<label class="mdl-textfield__label" for="processDefinition">{{'START_PROCESS.DIALOG.LABEL.TYPE'|translate}}</label>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" [(ngModel)]="name" rows="1" id="processName" />
<label class="mdl-textfield__label" for="processName">{{'START_PROCESS.DIALOG.LABEL.NAME'|translate}}</label>
</div>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="startProcess()" class="mdl-button">{{'START_PROCESS.DIALOG.ACTION.START'|translate}}</button>
<button type="button" (click)="cancel()" class="mdl-button close">{{'START_PROCESS.DIALOG.ACTION.CANCEL'|translate}}</button>
</div>
</dialog>

View File

@ -0,0 +1,101 @@
/*!
* @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, ViewChild } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.service';
declare let componentHandler: any;
declare let __moduleName: string;
@Component({
selector: 'activiti-start-process',
moduleId: __moduleName,
templateUrl: './activiti-start-process.component.html',
styleUrls: ['./activiti-start-process.component.css'],
providers: [ActivitiProcessService],
pipes: [ AlfrescoPipeTranslate ]
})
export class ActivitiStartProcessButton implements OnInit {
@Input()
appId: string;
@ViewChild('dialog')
dialog: any;
processDefinitions: any[] = [];
name: string;
processDefinition: string;
/**
* Constructor
* @param auth
* @param translate
* @param activitiProcess
*/
constructor(private auth: AlfrescoAuthenticationService,
private translate: AlfrescoTranslationService,
private activitiProcess: ActivitiProcessService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src');
}
}
ngOnInit() {
this.load(this.appId);
}
public load(appId: string) {
this.activitiProcess.getProcessDefinitions(this.appId).subscribe(
(res: any[]) => {
this.processDefinitions = res;
},
(err) => {
console.log(err);
}
);
}
public showDialog() {
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
public startProcess() {
if (this.processDefinition && this.name) {
this.activitiProcess.startProcess(this.processDefinition, this.name).subscribe(
(res: any) => {
this.cancel();
},
(err) => {
console.log(err);
}
);
}
}
public cancel() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
}
}

View File

@ -1,10 +1,54 @@
{
"PROCESSLIST": {
"NONE": "No active processes were found",
"SUMMARY": "Found {{total}} active process instances",
"ERROR": "An error occurred while loading the processes: {{errorMessage}}",
"NONE": "No process instances were found",
"SUMMARY": "Found {{total}} process instances",
"ERROR": "An error occurred while loading the processes instances: {{errorMessage}}",
"COLUMN": {
"NAME": "Name"
}
},
"FILTERS": {
"MESSAGES": {
"NONE": "No process instance filter selected."
}
},
"DETAILS": {
"LABELS": {
"STARTED_BY": "Started by",
"STARTED": "Started",
"COMMENTS": "Comments",
"TASKS_ACTIVE": "Active Tasks",
"TASKS_COMPLETED": "Completed Tasks",
"TASK_SUBTITLE": "Assigned to {{user}}, created {{created}}"
},
"BUTTON": {
"CANCEL": "Cancel Process"
},
"MESSAGES": {
"NONE": "No process details found."
},
"TASKS": {
"NO_ACTIVE": "No tasks are currently active",
"NO_COMPLETED": "No tasks have been completed yet",
"TASK_DETAILS": "Task details",
"TASK_CLOSE": "Close"
},
"COMMENTS": {
"NONE": "No comments."
}
},
"START_PROCESS": {
"BUTTON": "Start Process",
"DIALOG": {
"TITLE": "Start Process",
"LABEL": {
"TYPE": "Type",
"NAME": "Name"
},
"ACTION": {
"START": "Start",
"CANCEL": "Cancel"
}
}
}
}

View File

@ -0,0 +1,38 @@
/*!
* @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.
*/
/**
*
* Comment submitted against a process
*
* @returns {Comment} .
*/
import { User } from './user.model';
export class Comment {
id: number;
message: string;
created: string;
createdBy: User;
constructor(id: number, message: string, created: string, createdBy: User) {
this.id = id;
this.message = message;
this.created = created;
this.createdBy = createdBy;
}
}

View File

@ -0,0 +1,60 @@
/*!
* @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 filter.
*
*
* @returns {FilterModel} .
*/
export class FilterModel {
id: number;
name: string;
recent: boolean = false;
icon: string;
filter: FilterParamsModel;
appId: number;
constructor(name: string, recent: boolean, icon: string, query: string, state: string, assignment: string, appDefinitionId?: string) {
this.name = name;
this.recent = recent;
this.icon = icon;
this.filter = new FilterParamsModel(query, state, assignment, appDefinitionId);
}
}
/**
*
* This object represent the parameters of a filter.
*
*
* @returns {FilterModel} .
*/
export class FilterParamsModel {
name: string;
sort: string;
state: string;
appDefinitionId: string;
constructor(query: string, sort: string, state: string, appDefinitionId?: string) {
this.name = query;
this.sort = sort;
this.state = state;
this.appDefinitionId = appDefinitionId;
}
}

View File

@ -0,0 +1,93 @@
/*!
* @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} .
*/
import { User } from './user.model';
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;
}
}

View File

@ -0,0 +1,38 @@
/*!
* @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 user.
*
*
* @returns {User} .
*/
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

@ -17,6 +17,9 @@
import {AlfrescoAuthenticationService} from 'ng2-alfresco-core';
import {ProcessInstance} from '../models/process-instance';
import {FilterModel} from '../models/filter.model';
import {User} from '../models/user.model';
import {Comment} from '../models/comment.model';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
@ -28,6 +31,16 @@ export class ActivitiProcessService {
constructor(public authService: AlfrescoAuthenticationService) {
}
/**
* Retrive all the Deployed app
* @returns {Observable<any>}
*/
getDeployedApplications(name: string): Observable<any> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.appsApi.getAppDefinitions())
.map((response: any) => response.data.find(p => p.name === name))
.do(data => console.log('Application: ' + JSON.stringify(data)));
}
getProcesses(): Observable<ProcessInstance[]> {
let request = {'page': 0, 'sort': 'created-desc', 'state': 'all'};
@ -36,6 +49,109 @@ export class ActivitiProcessService {
.catch(this.handleError);
}
getProcessInstances(filter: FilterModel): Observable<ProcessInstance[]> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstances(filter))
.map(this.extractData)
.catch(this.handleError);
}
getProcessFilters(appId: string): Observable<any[]> {
let filterOpts = appId ? {
appId: appId
} : {};
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.userFiltersApi.getUserProcessInstanceFilters(filterOpts))
.map(this.extractData)
.catch(this.handleError);
}
getProcess(id: string): Observable<ProcessInstance> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstance(id))
.catch(this.handleError);
}
getProcessTasks(id: string, state: string): Observable<any[]> {
let taskOpts = state ? {
processInstanceId: id,
state: state
} : {
processInstanceId: id
};
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.listTasks(taskOpts))
.map(this.extractData)
.map(tasks => tasks.map((task: any) => {
task.created = new Date(task.created);
return task;
}))
.catch(this.handleError);
}
/**
* Retrive all the process instance's comments
* @param id - process instance ID
* @returns {<Comment[]>}
*/
getProcessInstanceComments(id: string): Observable<Comment[]> {
return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.commentsApi.getProcessInstanceComments(id))
.map(res => res)
.map((response: any) => {
let comments: Comment[] = [];
response.data.forEach((comment) => {
let user = new User(
comment.createdBy.id, comment.createdBy.email, comment.createdBy.firstName, comment.createdBy.lastName);
comments.push(new Comment(comment.id, comment.message, comment.created, user));
});
return comments;
}).catch(this.handleError);
}
/**
* Add a comment to a process instance
* @param id - process instance Id
* @param message - content of the comment
* @returns {Comment}
*/
addProcessInstanceComment(id: string, message: string): Observable<Comment> {
return Observable.fromPromise(
this.authService.getAlfrescoApi().activiti.commentsApi.addProcessInstanceComment({message: message}, id)
)
.map(res => res)
.map((response: Comment) => {
return new Comment(response.id, response.message, response.created, response.createdBy);
}).catch(this.handleError);
}
getProcessDefinitions(appId: string) {
let opts = appId ? {
latest: true,
appId: appId
} : {
latest: true
};
return Observable.fromPromise(
this.authService.getAlfrescoApi().activiti.processApi.getProcessDefinitions(opts)
)
.map(this.extractData)
.catch(this.handleError);
}
startProcess(processDefinitionId: string, name: string) {
let startRequest: any = {};
startRequest.name = name;
startRequest.processDefinitionId = processDefinitionId;
return Observable.fromPromise(
this.authService.getAlfrescoApi().activiti.processApi.startNewProcessInstance(startRequest)
)
.catch(this.handleError);
}
cancelProcess(processInstanceId: string) {
return Observable.fromPromise(
this.authService.getAlfrescoApi().activiti.processApi.deleteProcessInstance(processInstanceId)
)
.catch(this.handleError);
}
private extractData(res: any) {
return res.data || {};
}

View File

@ -37,8 +37,8 @@
"material-design-lite": "1.1.3",
"ng2-translate": "2.2.2",
"alfresco-js-api": "^0.3.0",
"ng2-alfresco-datatable": "^0.1.12",
"ng2-alfresco-core": "^0.1.36"
"ng2-alfresco-datatable": "^0.3.0",
"ng2-alfresco-core": "^0.3.0"
},
"devDependencies": {
"browser-sync": "2.10.0",

View File

@ -15,69 +15,70 @@
* limitations under the License.
*/
import {Component, OnInit} from '@angular/core';
import {ALFRESCO_CORE_PROVIDERS, AlfrescoAuthenticationService, AlfrescoSettingsService} from 'ng2-alfresco-core';
import {bootstrap} from '@angular/platform-browser-dynamic';
import {ActivitiTaskList} from 'ng2-activiti-tasklist';
import {ObjectDataTableAdapter, ObjectDataColumn} from 'ng2-alfresco-datatable';
import {HTTP_PROVIDERS} from '@angular/http';
declare let AlfrescoApi: any;
import { bootstrap } from '@angular/platform-browser-dynamic';
import { Component, OnInit, ViewChild } from '@angular/core';
import { ALFRESCO_CORE_PROVIDERS, AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core';
import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist';
import { HTTP_PROVIDERS } from '@angular/http';
import { ATIVITI_FORM_PROVIDERS } from 'ng2-activiti-form';
@Component({
selector: 'activiti-tasklist-demo',
template: `label for="token"><b>Insert a valid access token / ticket:</b></label><br>
template: `<label for="token"><b>Insert a valid access token / ticket:</b></label><br>
<input id="token" type="text" size="48" (change)="updateToken();documentList.reload()" [(ngModel)]="token"><br>
<label for="token"><b>Insert the ip of your Alfresco instance:</b></label><br>
<label for="token"><b>Insert the ip of your Activiti instance:</b></label><br>
<input id="token" type="text" size="48" (change)="updateHost();documentList.reload()" [(ngModel)]="bpmHost"><br><br>
<div *ngIf="!authenticated" style="color:#FF2323">
Authentication failed to ip {{ bpmHost }} with user: admin, admin, you can still try to add a valid token to perform
operations.
</div>
<hr>
<label for="token"><b>Insert a scriptPath</b></label><br>
<input id="token" type="text" size="48" [(ngModel)]="scriptPath"><br>
<label for="token"><b>Insert a contextRoot</b></label><br>
<input id="token" type="text" size="48" [(ngModel)]="contextRoot"><br>
<label for="token"><b>Insert a servicePath</b></label><br>
<input id="token" type="text" size="48" [(ngModel)]="servicePath"><br>
<div class="container" *ngIf="authenticated">
<activiti-tasklist></activiti-tasklist>
<span>Task Filters</span>
<activiti-filters (filterClick)="onFilterClick($event)"></activiti-filters>
<span>Tasks</span>
<activiti-tasklist [taskFilter]="taskFilter" [schemaColumn]="schemaColumn"
(rowClick)="onRowClick($event)" #activititasklist></activiti-tasklist>
<span>Task Details</span>
<activiti-task-details [taskId]="currentTaskId" #activitidetails></activiti-task-details>
</div>`,
styles: [
':host > .container {padding: 10px}',
'.p-10 { padding: 10px; }'
],
directives: [ActivitiTaskList]
directives: [ALFRESCO_TASKLIST_DIRECTIVES]
})
class ActivitiTaskListDemo implements OnInit {
@ViewChild('activititasklist')
activititasklist: any;
@ViewChild('activitidetails')
activitidetails: any;
bpmHost: string = 'http://127.0.0.1:9999';
token: string;
data: ObjectDataTableAdapter;
authenticated: boolean;
schemaColumn: any [] = [];
currentTaskId: string;
taskFilter: any;
constructor(private authService: AlfrescoAuthenticationService,
private settingsService: AlfrescoSettingsService) {
this.settingsService.setProviders('BPM');
this.data = new ObjectDataTableAdapter([], []);
}
ngOnInit() {
this.login();
let schema = [
{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}
this.schemaColumn = [
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}
];
let columns = schema.map(col => new ObjectDataColumn(col));
this.data.setColumns(columns);
}
login() {
@ -92,9 +93,20 @@ class ActivitiTaskListDemo implements OnInit {
this.authenticated = false;
});
}
onFilterClick(event: any) {
this.taskFilter = event;
this.activititasklist.load(this.taskFilter);
}
onRowClick(taskId) {
this.currentTaskId = taskId;
this.activitidetails.loadDetails(this.currentTaskId);
}
}
bootstrap(ActivitiTaskListDemo, [
HTTP_PROVIDERS,
ATIVITI_FORM_PROVIDERS,
ALFRESCO_CORE_PROVIDERS]
);

View File

@ -13,6 +13,7 @@
'ng2-translate': 'node_modules/ng2-translate',
'ng2-alfresco-core': 'node_modules/ng2-alfresco-core/dist',
'ng2-alfresco-datatable': 'node_modules/ng2-alfresco-datatable/dist',
'ng2-activiti-form': 'node_modules/ng2-activiti-form/dist',
'ng2-activiti-tasklist': 'node_modules/ng2-activiti-tasklist/dist'
};
// packages tells the System loader how to load when no filename and/or no extension
@ -24,6 +25,7 @@
'ng2-translate': { defaultExtension: 'js' },
'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' },
'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' },
'ng2-activiti-form': { main: 'index.js', defaultExtension: 'js' },
'ng2-activiti-tasklist': { main: 'index.js', defaultExtension: 'js' }
};
var ngPackageNames = [

View File

@ -1,7 +1,7 @@
{
"name": "ng2-activiti-tasklist",
"description": "Activiti Angular2 Task List Component",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
@ -67,9 +67,9 @@
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0",
"ng2-activiti-form": "0.2.0",
"ng2-alfresco-core": "0.3.0",
"ng2-alfresco-datatable": "0.3.0",
"ng2-activiti-form": "0.3.0",
"alfresco-js-api": "^0.3.0"
},
"peerDependencies": {

View File

@ -59,7 +59,7 @@ export class ActivitiChecklist implements OnInit {
private activitiTaskList: ActivitiTaskListService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
}
this.task$ = new Observable<TaskDetailsModel>(observer => this.taskObserver = observer).share();
}

View File

@ -59,7 +59,7 @@ export class ActivitiComments implements OnInit {
private activitiTaskList: ActivitiTaskListService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
}
this.comment$ = new Observable<Comment>(observer => this.commentObserver = observer).share();

View File

@ -70,7 +70,7 @@ export class ActivitiFilters implements OnInit {
this.filter$ = new Observable<FilterModel>(observer => this.filterObserver = observer).share();
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
}
}

View File

@ -52,7 +52,7 @@ export class ActivitiPeople implements OnInit {
private translate: AlfrescoTranslationService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
}
this.people$ = new Observable<User>(observer => this.peopleObserver = observer).share();
}

View File

@ -92,7 +92,7 @@ export class ActivitiTaskDetails implements OnInit {
private activitiTaskList: ActivitiTaskListService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
}
}
@ -125,6 +125,8 @@ export class ActivitiTaskDetails implements OnInit {
console.log(this.taskDetails);
}
);
} else {
this.taskDetails = null;
}
}

View File

@ -49,7 +49,7 @@ export class ActivitiTaskHeader implements OnInit, OnChanges {
private translate: AlfrescoTranslationService) {
if (translate) {
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist');
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
}
}

View File

@ -15,11 +15,11 @@
* limitations under the License.
*/
import {it, describe, inject, beforeEach, beforeEachProviders} from '@angular/core/testing';
import {ActivitiTaskListService} from './activiti-tasklist.service';
import {AlfrescoSettingsService, AlfrescoAuthenticationService} from 'ng2-alfresco-core';
import {TaskDetailsModel} from '../models/task-details.model';
import {Comment} from '../models/comment.model';
import { it, describe, inject, beforeEach, beforeEachProviders } from '@angular/core/testing';
import { ActivitiTaskListService } from './activiti-tasklist.service';
import { AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { TaskDetailsModel } from '../models/task-details.model';
import { Comment } from '../models/comment.model';
declare let AlfrescoApi: any;
declare let jasmine: any;

View File

@ -1,7 +1,7 @@
{
"name": "ng2-alfresco-core",
"description": "Alfresco Angular 2 Components core",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",

View File

@ -33,9 +33,9 @@ export class AlfrescoSettingsService {
private providers: string = 'ALL'; // ECM, BPM , ALL
bpmHostSubject: Subject<string> = new Subject<string>();
ecmHostSubject: Subject<string> = new Subject<string>();
providerSubject: Subject<string> = new Subject<string>();
public bpmHostSubject: Subject<string> = new Subject<string>();
public ecmHostSubject: Subject<string> = new Subject<string>();
public providerSubject: Subject<string> = new Subject<string>();
public get ecmHost(): string {
return this._ecmHost;

View File

@ -41,8 +41,7 @@
"license-check": "1.1.5",
"material-design-icons": "2.2.3",
"material-design-lite": "1.1.3",
"ng2-activiti-processlist": "^0.1.0"
"material-design-lite": "1.1.3"
},
"devDependencies": {
"browser-sync": "2.10.0",

View File

@ -1,7 +1,7 @@
{
"name": "ng2-alfresco-datatable",
"description": "Alfresco Angular2 DataTable Component",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
@ -63,7 +63,7 @@
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0"
"ng2-alfresco-core": "0.3.0"
},
"peerDependencies": {
"material-design-icons": "^2.2.3",

View File

@ -47,7 +47,11 @@
[context-menu]="getContextMenuActions(row, col)">
<div *ngSwitchCase="'image'" class="cell-value">
<i *ngIf="isIconValue(row, col)" class="material-icons icon-cell">{{asIconValue(row, col)}}</i>
<img *ngIf="!isIconValue(row, col)" class="image-cell" alt="{{iconAltTextKey(data.getValue(row, col))|translate}}" src="{{data.getValue(row, col)}}">
<img *ngIf="!isIconValue(row, col)"
class="image-cell"
alt="{{iconAltTextKey(data.getValue(row, col))|translate}}"
src="{{data.getValue(row, col)}}"
(error)="onImageLoadingError($event)">
</div>
<div *ngSwitchCase="'date'" class="cell-value" [attr.data-automation-id]="'date_' + data.getValue(row, col)">
{{data.getValue(row, col)}}

View File

@ -320,4 +320,29 @@ describe('DataTable', () => {
expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_1'}, 'asc')).toBeTruthy();
expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_2'}, 'desc')).toBeFalsy();
});
it('should replace image source with fallback thumbnail on error', () => {
let event = <any> {
srcElement: {
src: 'missing-image'
}
};
dataTable.fallbackThumbnail = '<fallback>';
dataTable.onImageLoadingError(event);
expect(event.srcElement.src).toBe(dataTable.fallbackThumbnail);
});
it('should replace image source only when fallback available', () => {
const originalSrc = 'missing-image';
let event = <any> {
srcElement: {
src: originalSrc
}
};
dataTable.fallbackThumbnail = null;
dataTable.onImageLoadingError(event);
expect(event.srcElement.src).toBe(originalSrc);
});
});

View File

@ -63,6 +63,9 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
@Input()
actions: boolean = false;
@Input()
fallbackThumbnail: string;
@Output()
rowClick: EventEmitter<DataRowEvent> = new EventEmitter<DataRowEvent>();
@ -155,6 +158,13 @@ export class DataTableComponent implements OnInit, AfterViewChecked {
}
}
onImageLoadingError(event: Event) {
if (event && this.fallbackThumbnail) {
let element = <any> event.srcElement;
element.src = this.fallbackThumbnail;
}
}
isIconValue(row: DataRow, col: DataColumn) {
if (row && col) {
let value = row.getValue(col.key);

View File

@ -12,6 +12,10 @@
<link rel="stylesheet" href="node_modules/material-design-icons/iconfont/material-icons.css">
<!-- 1. Load libraries -->
<!-- Polyfill(s) for Safari (pre-10.x) -->
<script src="node_modules/intl/dist/Intl.min.js"></script>
<script src="node_modules/intl/locale-data/jsonp/en.js"></script>
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>

View File

@ -36,10 +36,11 @@
"material-design-icons": "2.2.3",
"material-design-lite": "1.1.3",
"ng2-translate": "2.2.2",
"intl": "1.2.4",
"alfresco-js-api": "^0.3.0",
"ng2-alfresco-core": "^0.2.0",
"ng2-alfresco-documentlist": "^0.2.0",
"ng2-alfresco-datatable": "^0.2.0"
"ng2-alfresco-core": "^0.3.0",
"ng2-alfresco-documentlist": "^0.3.0",
"ng2-alfresco-datatable": "^0.3.0"
},
"devDependencies": {
"concurrently": "2.0.0",

View File

@ -37,17 +37,15 @@ import {
@Component({
selector: 'alfresco-documentlist-demo',
template: `
<label for="token"><b>Insert a valid access token / ticket:</b></label><br>
<input id="token" type="text" size="48" (change)="updateToken();documentList.reload()" [(ngModel)]="token"><br>
<label for="token"><b>Insert the ip of your Alfresco instance:</b></label><br>
<input id="token" type="text" size="48" (change)="updateHost();documentList.reload()" [(ngModel)]="host"><br><br>
<label for="ticket"><b>Insert a valid access ticket / ticket:</b></label><br>
<input id="ticket" type="text" size="48" (change)="updateTicket(); documentList.reload()" [(ngModel)]="ticket"><br>
<label for="host"><b>Insert the ip of your Alfresco instance:</b></label><br>
<input id="host" type="text" size="48" (change)="updateHost(); documentList.reload()" [(ngModel)]="ecmHost"><br><br>
<div *ngIf="!authenticated" style="color:#FF2323">
Authentication failed to ip {{ host }} with user: admin, admin, you can still try to add a valid token to perform
Authentication failed to ip {{ ecmHost }} with user: admin, admin, you can still try to add a valid ticket to perform
operations.
</div>
<hr>
<div class="container" *ngIf="authenticated">
<alfresco-document-list-breadcrumb
[currentFolderPath]="currentPath"
[target]="documentList">
@ -57,7 +55,6 @@ import {
[currentFolderPath]="currentPath"
[contextMenuActions]="true"
[contentActions]="true"
[multiselect]="true"
(folderChange)="onFolderChanged($event)">
<!--
<empty-folder-content>
@ -74,6 +71,12 @@ import {
sortable="true"
class="full-width ellipsis-cell">
</content-column>
<!--
<content-column
title="Type"
source="content.mimeType">
</content-column>
-->
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
key="createdByUser.displayName"
@ -89,6 +92,7 @@ import {
class="desktop-only">
</content-column>
</content-columns>
<content-actions>
<!-- folder actions -->
<content-action
@ -106,7 +110,6 @@ import {
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
handler="delete">
</content-action>
<!-- document actions -->
<content-action
target="document"
@ -128,10 +131,14 @@ import {
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
handler="delete">
</content-action>
<content-action
target="folder"
title="Activiti: View Form"
(execute)="viewActivitiForm($event)">
</content-action>
</content-actions>
</alfresco-document-list>
<context-menu-holder></context-menu-holder>
</div>
`,
styles: [':host > .container {padding: 10px}'],
directives: [DOCUMENT_LIST_DIRECTIVES, CONTEXT_MENU_DIRECTIVES],
@ -141,28 +148,27 @@ import {
class DocumentListDemo implements OnInit {
currentPath: string = '/';
authenticated: boolean;
authenticated: boolean = false;
ecmHost: string = 'http://devproducts-platform.alfresco.me';
token: string;
ticket: string;
constructor(
private authService: AlfrescoAuthenticationService,
private settingsService: AlfrescoSettingsService,
translation: AlfrescoTranslationService,
private documentActions: DocumentActionsService) {
constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService,
translation: AlfrescoTranslationService, private documentActions: DocumentActionsService) {
settingsService.ecmHost = this.ecmHost;
if (this.authService.getTicket()) {
this.token = this.authService.getTicket();
settingsService.setProviders('ECM');
if (this.authService.getTicketEcm()) {
this.ticket = this.authService.getTicketEcm();
}
translation.addTranslationFolder();
documentActions.setHandler('my-handler', this.myDocumentActionHandler.bind(this));
}
public updateToken(): void {
localStorage.setItem('token', this.token);
public updateTicket(): void {
localStorage.setItem('ticket-ECM', this.ticket);
}
public updateHost(): void {
@ -190,9 +196,9 @@ class DocumentListDemo implements OnInit {
login() {
this.authService.login('admin', 'admin').subscribe(
token => {
console.log(token);
this.token = token;
ticket => {
console.log(ticket);
this.ticket = this.authService.getTicketEcm();
this.authenticated = true;
},
error => {

View File

@ -72,7 +72,7 @@ module.exports = function (config) {
],
// Coverage reporter generates the coverage
reporters: ['mocha', 'coverage', 'coveralls', 'kjhtml'],
reporters: ['mocha', 'coverage', 'kjhtml'],
// Source files that you wanna generate coverage for.
// Do not include tests or libraries (these files will be instrumented by Istanbul)

View File

@ -1,7 +1,7 @@
{
"name": "ng2-alfresco-documentlist",
"description": "Alfresco Angular2 Document List Component",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
@ -70,8 +70,8 @@
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0",
"ng2-alfresco-core": "0.3.0",
"ng2-alfresco-datatable": "0.3.0",
"alfresco-js-api": "^0.3.0"
},
"peerDependencies": {

View File

@ -0,0 +1,44 @@
.document-list_empty_template {
text-align: center;
margin-top: 20px;
margin-bottom: 20px;
}
.document-list__this-space-is-empty {
height: 32px;
opacity: 0.26;
font-family: Muli, Helvetica, Arial, sans-serif;
font-size: 24px;
line-height: 1.33;
letter-spacing: -1px;
color: #000000;
}
.document-list__drag-drop {
height: 56px;
opacity: 0.54;
font-family: Muli, Helvetica, Arial, sans-serif;
font-size: 56px;
line-height: 1;
letter-spacing: -2px;
color: #000000;
margin-top: 40px;
}
.document-list__any-files-here-to-add {
height: 24px;
opacity: 0.54;
font-family: Muli, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
letter-spacing: -0.4px;
color: #000000;
margin-top: 17px;
}
.document-list__empty_doc_lib {
width: 565px;
height: 161px;
object-fit: contain;
margin-top: 17px;
}

Some files were not shown because too many files have changed in this diff Show More