@ -25,6 +25,7 @@ env:
|
||||
- MODULE=ng2-activiti-diagrams
|
||||
- MODULE=ng2-activiti-analytics
|
||||
- MODULE=ng2-alfresco-userinfo
|
||||
- MODULE=ng2-alfresco-social
|
||||
|
||||
before_script:
|
||||
- if ([ "$MODULE" != "ng2-alfresco-core" ]); then
|
||||
|
41
Jenkinsfile
vendored
@ -1,41 +0,0 @@
|
||||
#!/usr/bin/groovy
|
||||
@Library('github.com/fabric8io/fabric8-pipeline-library@v2.2.311')
|
||||
def utils = new io.fabric8.Utils()
|
||||
clientsNode{
|
||||
def envStage = utils.environmentNamespace('staging')
|
||||
def envProd = utils.environmentNamespace('production')
|
||||
def newVersion = ''
|
||||
|
||||
git 'https://github.com/Alfresco/alfresco-ng2-components.git'
|
||||
|
||||
stage 'Canary release'
|
||||
echo 'NOTE: running pipelines for the first time will take longer as build and base docker images are pulled onto the node'
|
||||
if (!fileExists ('Dockerfile')) {
|
||||
writeFile file: 'Dockerfile', text: 'FROM node:5.3-onbuild'
|
||||
}
|
||||
|
||||
newVersion = performCanaryRelease {}
|
||||
|
||||
def rc = getKubernetesJson {
|
||||
port = 8080
|
||||
label = 'node'
|
||||
icon = 'https://cdn.rawgit.com/fabric8io/fabric8/dc05040/website/src/images/logos/nodejs.svg'
|
||||
version = newVersion
|
||||
imageName = clusterImageName
|
||||
}
|
||||
|
||||
stage 'Rollout Staging'
|
||||
kubernetesApply(file: rc, environment: envStage)
|
||||
|
||||
stage 'Approve'
|
||||
approve{
|
||||
room = null
|
||||
version = canaryVersion
|
||||
console = fabric8Console
|
||||
environment = envStage
|
||||
}
|
||||
|
||||
stage 'Rollout Production'
|
||||
kubernetesApply(file: rc, environment: envProd)
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ environment:
|
||||
- COMPONENT_NAME: ng2-activiti-diagrams
|
||||
- COMPONENT_NAME: ng2-activiti-analytics
|
||||
- COMPONENT_NAME: ng2-alfresco-userinfo
|
||||
- COMPONENT_NAME: ng2-alfresco-social
|
||||
|
||||
# Install scripts. (runs after repo cloning)
|
||||
install:
|
||||
|
@ -58,6 +58,7 @@
|
||||
<a class="mdl-navigation__link" href="" routerLink="/activiti" (click)="hideDrawer()">Process Services</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/webscript" (click)="hideDrawer()">Webscript</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/tag" (click)="hideDrawer()">Tag</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/social" (click)="hideDrawer()">Social</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/about" (click)="hideDrawer()">About</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/settings" (click)="hideDrawer()">Settings</a>
|
||||
</nav>
|
||||
|
@ -25,6 +25,7 @@ import { DataTableModule } from 'ng2-alfresco-datatable';
|
||||
import { DocumentListModule } from 'ng2-alfresco-documentlist';
|
||||
import { UploadModule } from 'ng2-alfresco-upload';
|
||||
import { TagModule } from 'ng2-alfresco-tag';
|
||||
import { SocialModule } from 'ng2-alfresco-social';
|
||||
import { WebScriptModule } from 'ng2-alfresco-webscript';
|
||||
import { ViewerModule } from 'ng2-alfresco-viewer';
|
||||
import { ActivitiFormModule } from 'ng2-activiti-form';
|
||||
@ -50,6 +51,7 @@ import {
|
||||
FormViewer,
|
||||
WebscriptComponent,
|
||||
TagComponent,
|
||||
SocialComponent,
|
||||
AboutComponent,
|
||||
FilesComponent,
|
||||
FormNodeViewer,
|
||||
@ -68,6 +70,7 @@ import {
|
||||
DocumentListModule.forRoot(),
|
||||
UploadModule.forRoot(),
|
||||
TagModule.forRoot(),
|
||||
SocialModule.forRoot(),
|
||||
WebScriptModule.forRoot(),
|
||||
ViewerModule.forRoot(),
|
||||
ActivitiFormModule.forRoot(),
|
||||
@ -91,6 +94,7 @@ import {
|
||||
FormViewer,
|
||||
WebscriptComponent,
|
||||
TagComponent,
|
||||
SocialComponent,
|
||||
AboutComponent,
|
||||
FilesComponent,
|
||||
FormNodeViewer,
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
ActivitiAppsView,
|
||||
WebscriptComponent,
|
||||
TagComponent,
|
||||
SocialComponent,
|
||||
AboutComponent,
|
||||
FormViewer,
|
||||
FormNodeViewer,
|
||||
@ -117,6 +118,11 @@ export const appRoutes: Routes = [
|
||||
component: TagComponent,
|
||||
canActivate: [AuthGuardEcm]
|
||||
},
|
||||
{
|
||||
path: 'social',
|
||||
component: SocialComponent,
|
||||
canActivate: [AuthGuardEcm]
|
||||
},
|
||||
{ path: 'about', component: AboutComponent },
|
||||
{ path: 'settings', component: SettingComponent }
|
||||
];
|
||||
|
@ -21,6 +21,6 @@
|
||||
}
|
||||
|
||||
.list-buttons {
|
||||
text-align: right;
|
||||
text-align: left;
|
||||
margin-bottom: 5px;
|
||||
}
|
@ -19,20 +19,25 @@
|
||||
<div class="page-content">
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--2-col task-column mdl-shadow--2dp">
|
||||
<activiti-filters
|
||||
[appId]="appId"
|
||||
(filterClick)="onTaskFilterClick($event)"
|
||||
(onSuccess)="onSuccessTaskFilterList($event)"
|
||||
#activitifilter>
|
||||
</activiti-filters>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--3-col task-column mdl-shadow--2dp list-column">
|
||||
<div class="list-buttons">
|
||||
<activiti-start-task
|
||||
[appId]="appId"
|
||||
(onSuccess)="onStartTaskSuccess($event)">
|
||||
</activiti-start-task>
|
||||
</div>
|
||||
<adf-accordion>
|
||||
<adf-accordion-group [heading]="'Tasks'" [isSelected]="true" [isOpen]="true" [headingIcon]="'assignment'">
|
||||
<activiti-filters
|
||||
[appId]="appId"
|
||||
[hasIcon]="false"
|
||||
(filterClick)="onTaskFilterClick($event)"
|
||||
(onSuccess)="onSuccessTaskFilterList($event)"
|
||||
#activitifilter>
|
||||
</activiti-filters>
|
||||
</adf-accordion-group>
|
||||
</adf-accordion>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--3-col task-column mdl-shadow--2dp list-column">
|
||||
<activiti-tasklist
|
||||
[appId]="taskFilter?.appId"
|
||||
[processDefinitionKey]="taskFilter?.filter?.processDefinitionKey"
|
||||
@ -58,10 +63,12 @@
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--7-col task-column mdl-shadow--2dp">
|
||||
<activiti-task-details #activitidetails
|
||||
[debugMode]="true"
|
||||
[taskId]="currentTaskId"
|
||||
(formCompleted)="onFormCompleted($event)"
|
||||
(formContentClicked)="onFormContentClick($event)"
|
||||
(taskCreated)="onTaskCreated($event)">
|
||||
(taskCreated)="onTaskCreated($event)"
|
||||
(taskDeleted)="onTaskDeleted($event)">
|
||||
</activiti-task-details>
|
||||
</div>
|
||||
</div>
|
||||
@ -76,13 +83,6 @@
|
||||
<div class="page-content">
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--2-col task-column mdl-shadow--2dp">
|
||||
<activiti-process-instance-filters
|
||||
[appId]="appId"
|
||||
(filterClick)="onProcessFilterClick($event)"
|
||||
(onSuccess)="onSuccessProcessFilterList($event)">
|
||||
</activiti-process-instance-filters>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--3-col task-column list-column mdl-shadow--2dp" *ngIf="processFilter && !isStartProcessMode()">
|
||||
<div class="list-buttons">
|
||||
<button
|
||||
md-raised-button
|
||||
@ -92,6 +92,17 @@
|
||||
<span>START PROCESS</span>
|
||||
</button>
|
||||
</div>
|
||||
<adf-accordion>
|
||||
<adf-accordion-group [heading]="'Processes'" [isSelected]="true" [isOpen]="true" [headingIcon]="'assessment'">
|
||||
<activiti-process-instance-filters
|
||||
[appId]="appId"
|
||||
(filterClick)="onProcessFilterClick($event)"
|
||||
(onSuccess)="onSuccessProcessFilterList($event)">
|
||||
</activiti-process-instance-filters>
|
||||
</adf-accordion-group>
|
||||
</adf-accordion>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--3-col task-column list-column mdl-shadow--2dp" *ngIf="processFilter && !isStartProcessMode()">
|
||||
<activiti-process-instance-list
|
||||
*ngIf="processFilter?.hasFilter()" [appId]="processFilter.appId"
|
||||
[processDefinitionKey]="processFilter.filter.processDefinitionKey"
|
||||
|
@ -264,6 +264,10 @@ export class ActivitiDemoComponent implements AfterViewInit {
|
||||
this.taskList.reload();
|
||||
}
|
||||
|
||||
onTaskDeleted(data: any) {
|
||||
this.taskList.reload();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
// workaround for MDL issues with dynamic components
|
||||
if (componentHandler) {
|
||||
|
@ -15,8 +15,3 @@
|
||||
.error-message--text {
|
||||
color: #d50000;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
width: 250px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
<div class="container">
|
||||
<alfresco-upload-drag-area
|
||||
[rootFolderId]="documentList.currentFolderId"
|
||||
[versioning] = "versioning"
|
||||
(onSuccess)="documentList.reload()">
|
||||
[versioning] = "versioning">
|
||||
<alfresco-document-list-breadcrumb
|
||||
[target]="documentList"
|
||||
[folderNode]="documentList.folderNode">
|
||||
@ -21,19 +20,29 @@
|
||||
[allowDropFiles]="true"
|
||||
(error)="onNavigationError($event)"
|
||||
(success)="resetError()"
|
||||
(preview)="showFile($event)">
|
||||
(preview)="showFile($event)"
|
||||
(permissionError)="onPermissionsFailed($event)">
|
||||
<data-columns>
|
||||
<data-column key="$thumbnail" type="image" [sortable]="false"></data-column>
|
||||
<data-column
|
||||
title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
|
||||
key="name"
|
||||
class="full-width ellipsis-cell">
|
||||
<!-- Example of using custom column template -->
|
||||
|
||||
<!-- Example #1: using custom template with implicit access to data context -->
|
||||
<!--
|
||||
<template let-entry="$implicit">
|
||||
<span>Hi! {{entry.data.getValue(entry.row, entry.col)}}</span>
|
||||
</template>
|
||||
-->
|
||||
|
||||
<!-- Example #2: using custom template with value access -->
|
||||
<!--
|
||||
<template let-value="value">
|
||||
<span>Hi! {{value}}</span>
|
||||
</template>
|
||||
-->
|
||||
|
||||
</data-column>
|
||||
<data-column
|
||||
title="{{'DOCUMENT_LIST.COLUMNS.TAG' | translate}}"
|
||||
@ -71,7 +80,10 @@
|
||||
</content-action>
|
||||
<content-action
|
||||
target="folder"
|
||||
permission="delete"
|
||||
[disableWithNoPermission]="true"
|
||||
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
|
||||
(permissionEvent)="onPermissionsFailed($event)"
|
||||
handler="delete">
|
||||
</content-action>
|
||||
<!-- document actions -->
|
||||
@ -92,6 +104,9 @@
|
||||
</content-action>
|
||||
<content-action
|
||||
target="document"
|
||||
permission="delete"
|
||||
[disableWithNoPermission]="true"
|
||||
(permissionEvent)="onPermissionsFailed($event)"
|
||||
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
|
||||
handler="delete">
|
||||
</content-action>
|
||||
@ -107,64 +122,65 @@
|
||||
|
||||
<context-menu-holder></context-menu-holder>
|
||||
|
||||
<p class="options-container">
|
||||
<label for="switch-multiple-file" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input type="checkbox" id="switch-multiple-file" class="mdl-switch__input" (change)="toggleMultipleFileUpload()" >
|
||||
<span class="mdl-switch__label">Multiple File Upload</span>
|
||||
</label>
|
||||
</p>
|
||||
<div class="container">
|
||||
<section>
|
||||
<md-slide-toggle [(ngModel)]="multipleFileUpload">Multiple File Upload</md-slide-toggle>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<md-slide-toggle [(ngModel)]="folderUpload">Folder upload</md-slide-toggle>
|
||||
</section>
|
||||
|
||||
<p class="options-container">
|
||||
<label for="switch-folder-upload" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input type="checkbox" id="switch-folder-upload" class="mdl-switch__input" (change)="toggleFolder()">
|
||||
<span class="mdl-switch__label">Folder Upload</span>
|
||||
</label>
|
||||
</p>
|
||||
<section>
|
||||
<md-slide-toggle [(ngModel)]="acceptedFilesTypeShow">Custom extensions filter</md-slide-toggle>
|
||||
</section>
|
||||
|
||||
<p class="options-container">
|
||||
<label for="switch-accepted-file-type" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input type="checkbox" id="switch-accepted-file-type" class="mdl-switch__input" (change)="toggleAcceptedFilesType()">
|
||||
<span class="mdl-switch__label">Filter extension</span>
|
||||
</label>
|
||||
</p>
|
||||
<section>
|
||||
<md-slide-toggle [(ngModel)]="versioning">Enable versioning</md-slide-toggle>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<section>
|
||||
<md-slide-toggle [(ngModel)]="disableWithNoPermission">Disable when user has no permissions</md-slide-toggle>
|
||||
</section>
|
||||
|
||||
<h5>Upload</h5>
|
||||
<br>
|
||||
<div *ngIf="acceptedFilesTypeShow">
|
||||
<label class="mdl-input__label">Extension accepted
|
||||
<input type="text" data-automation-id="accepted-files-type" [(ngModel)]="acceptedFilesType">
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div *ngIf="!acceptedFilesTypeShow">
|
||||
<alfresco-upload-button data-automation-id="multiple-file-upload"
|
||||
<h5>Upload</h5>
|
||||
<section *ngIf="acceptedFilesTypeShow">
|
||||
<md-input-container>
|
||||
<input md-input placeholder="Extension accepted" [(ngModel)]="acceptedFilesType" data-automation-id="accepted-files-type">
|
||||
</md-input-container>
|
||||
</section>
|
||||
<div *ngIf="!acceptedFilesTypeShow">
|
||||
<alfresco-upload-button
|
||||
#uploadButton
|
||||
[disabled]="!enableUpload"
|
||||
data-automation-id="multiple-file-upload"
|
||||
[rootFolderId]="documentList.currentFolderId"
|
||||
[multipleFiles]="multipleFileUpload"
|
||||
[uploadFolders]="folderUpload"
|
||||
[versioning] = "versioning"
|
||||
(onSuccess)="documentList.reload()">
|
||||
<div class="mdl-spinner mdl-js-spinner is-active"></div>
|
||||
[versioning]="versioning"
|
||||
[disableWithNoPermission]="disableWithNoPermission"
|
||||
(permissionEvent)="onUploadPermissionFailed($event)">
|
||||
</alfresco-upload-button>
|
||||
</div>
|
||||
<div *ngIf="acceptedFilesTypeShow">
|
||||
<alfresco-upload-button data-automation-id="multiple-file-upload"
|
||||
</div>
|
||||
<div *ngIf="acceptedFilesTypeShow">
|
||||
<alfresco-upload-button
|
||||
#uploadButton
|
||||
[disabled]="!enableUpload"
|
||||
data-automation-id="multiple-file-upload"
|
||||
[rootFolderId]="documentList.currentFolderId"
|
||||
[acceptedFilesType]="acceptedFilesType"
|
||||
[multipleFiles]="multipleFileUpload"
|
||||
[uploadFolders]="folderUpload"
|
||||
[versioning] = "versioning"
|
||||
(onSuccess)="documentList.reload()">
|
||||
<div class="mdl-spinner mdl-js-spinner is-active"></div>
|
||||
[versioning]="versioning"
|
||||
[disableWithNoPermission]="disableWithNoPermission"
|
||||
(permissionEvent)="onUploadPermissionFailed($event)">
|
||||
</alfresco-upload-button>
|
||||
</div>
|
||||
<section>
|
||||
<md-checkbox [(ngModel)]="enableUpload">Enable upload (demoing enabled/disabled state)</md-checkbox>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<file-uploading-dialog #fileDialog></file-uploading-dialog>
|
||||
|
||||
<div *ngIf="fileShowed">
|
||||
|
@ -15,39 +15,63 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Optional, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, Input, OnInit, AfterViewInit, Optional, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { AlfrescoAuthenticationService, LogService } from 'ng2-alfresco-core';
|
||||
import { AlfrescoAuthenticationService, LogService, NotificationService } from 'ng2-alfresco-core';
|
||||
import { DocumentActionsService, DocumentListComponent, ContentActionHandler, DocumentActionModel, FolderActionModel } from 'ng2-alfresco-documentlist';
|
||||
import { FormService } from 'ng2-activiti-form';
|
||||
import { UploadButtonComponent, UploadDragAreaComponent } from 'ng2-alfresco-upload';
|
||||
|
||||
@Component({
|
||||
selector: 'files-component',
|
||||
templateUrl: './files.component.html',
|
||||
styleUrls: ['./files.component.css']
|
||||
})
|
||||
export class FilesComponent implements OnInit {
|
||||
export class FilesComponent implements OnInit, AfterViewInit {
|
||||
// The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root-
|
||||
currentFolderId: string = '-my-';
|
||||
|
||||
errorMessage: string = null;
|
||||
fileNodeId: any;
|
||||
fileShowed: boolean = false;
|
||||
|
||||
@Input()
|
||||
multipleFileUpload: boolean = false;
|
||||
|
||||
@Input()
|
||||
disableWithNoPermission: boolean = false;
|
||||
|
||||
@Input()
|
||||
folderUpload: boolean = false;
|
||||
|
||||
@Input()
|
||||
acceptedFilesTypeShow: boolean = false;
|
||||
|
||||
@Input()
|
||||
versioning: boolean = false;
|
||||
|
||||
@Input()
|
||||
acceptedFilesType: string = '.jpg,.pdf,.js';
|
||||
|
||||
@Input()
|
||||
enableUpload: boolean = true;
|
||||
|
||||
@ViewChild(DocumentListComponent)
|
||||
documentList: DocumentListComponent;
|
||||
|
||||
@ViewChild(UploadButtonComponent)
|
||||
uploadButton: UploadButtonComponent;
|
||||
|
||||
@ViewChild(UploadDragAreaComponent)
|
||||
uploadDragArea: UploadDragAreaComponent;
|
||||
|
||||
constructor(private documentActions: DocumentActionsService,
|
||||
private authService: AlfrescoAuthenticationService,
|
||||
private formService: FormService,
|
||||
private logService: LogService,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private router: Router,
|
||||
private notificationService: NotificationService,
|
||||
@Optional() private route: ActivatedRoute) {
|
||||
documentActions.setHandler('my-handler', this.myDocumentActionHandler.bind(this));
|
||||
}
|
||||
@ -73,27 +97,12 @@ export class FilesComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
toggleMultipleFileUpload() {
|
||||
this.multipleFileUpload = !this.multipleFileUpload;
|
||||
return this.multipleFileUpload;
|
||||
}
|
||||
|
||||
toggleFolder() {
|
||||
this.multipleFileUpload = false;
|
||||
this.folderUpload = !this.folderUpload;
|
||||
return this.folderUpload;
|
||||
}
|
||||
|
||||
toggleAcceptedFilesType() {
|
||||
this.acceptedFilesTypeShow = !this.acceptedFilesTypeShow;
|
||||
return this.acceptedFilesTypeShow;
|
||||
}
|
||||
|
||||
toggleVersioning() {
|
||||
this.versioning = !this.versioning;
|
||||
return this.versioning;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.route) {
|
||||
this.route.params.forEach((params: Params) => {
|
||||
@ -113,6 +122,20 @@ export class FilesComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.uploadButton.onSuccess
|
||||
.debounceTime(100)
|
||||
.subscribe((event) => {
|
||||
this.reload(event);
|
||||
});
|
||||
|
||||
this.uploadDragArea.onSuccess
|
||||
.debounceTime(100)
|
||||
.subscribe((event) => {
|
||||
this.reload(event);
|
||||
});
|
||||
}
|
||||
|
||||
viewActivitiForm(event?: any) {
|
||||
this.router.navigate(['/activiti/tasksnode', event.value.entry.id]);
|
||||
}
|
||||
@ -146,4 +169,20 @@ export class FilesComponent implements OnInit {
|
||||
window.alert(`Starting BPM process: ${processDefinition.id}`);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
onPermissionsFailed(event: any) {
|
||||
this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000);
|
||||
}
|
||||
|
||||
onUploadPermissionFailed(event: any) {
|
||||
this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000);
|
||||
}
|
||||
|
||||
reload(event: any) {
|
||||
if (event && event.value && event.value.entry && event.value.entry.parentId) {
|
||||
if (this.documentList.currentFolderId === event.value.entry.parentId) {
|
||||
this.documentList.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ export { ActivitiDemoComponent } from './activiti/activiti-demo.component';
|
||||
export { FormViewer } from './activiti/form-viewer.component';
|
||||
export { WebscriptComponent } from './webscript/webscript.component';
|
||||
export { TagComponent } from './tag/tag.component';
|
||||
export { SocialComponent } from './social/social.component';
|
||||
export { AboutComponent } from './about/about.component';
|
||||
export { FilesComponent } from './files/files.component';
|
||||
export { FormNodeViewer } from './activiti/form-node-viewer.component';
|
||||
|
@ -29,3 +29,7 @@
|
||||
.table-row {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.adf-setting-input-padding{
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
|
@ -10,18 +10,31 @@
|
||||
Content Services host URL configuration
|
||||
</div>
|
||||
<nav class="mdl-navigation">
|
||||
<div class="icon material-icons icon-margin">link</div>
|
||||
<input type="text" class="mdl-textfield__input" id="ecmHost" data-automation-id="ecmHost"
|
||||
tabindex="1" (change)="onChangeECMHost($event)" value="{{ecmHost}}"/>
|
||||
<i class="icon material-icons icon-margin">link</i>
|
||||
<div class="mdl-textfield mdl-js-textfield adf-setting-input-padding">
|
||||
<input data-automation-id="ecmHost"
|
||||
class="mdl-textfield__input" tabindex="1"
|
||||
type="text" tabindex="1"
|
||||
(change)="onChangeECMHost($event)"
|
||||
pattern="^(http|https):\/\/.*" id="ecmHost" value="{{ecmHost}}">
|
||||
<label class="mdl-textfield__label" for="ecmHost">ECM Host</label>
|
||||
<span class="mdl-textfield__error">ECM host is not valid!</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="mdl-card__supporting-text">
|
||||
Process Services host URL configuration
|
||||
</div>
|
||||
<nav class="mdl-navigation">
|
||||
<div class="icon material-icons icon-margin">link</div>
|
||||
<input type="text" class="mdl-textfield__input" id="bpmHost" data-automation-id="bpmHost"
|
||||
tabindex="1" (change)="onChangeBPMHost($event)" value="{{bpmHost}}"/>
|
||||
<i class="icon material-icons icon-margin">link</i>
|
||||
<div class="mdl-textfield mdl-js-textfield adf-setting-input-padding">
|
||||
<input class="mdl-textfield__input"
|
||||
type="text"
|
||||
(change)="onChangeBPMHost($event)"
|
||||
tabindex="2" pattern="^(http|https):\/\/.*" id="bpmHost" value="{{bpmHost}}">
|
||||
<label class="mdl-textfield__label" for="bpmHost">BPM Host</label>
|
||||
<span class="mdl-textfield__error">BPM host is not valid!</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="mdl-card__actions mdl-card--border">
|
||||
|
@ -15,15 +15,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, AfterViewChecked } from '@angular/core';
|
||||
import { AlfrescoSettingsService, StorageService, LogService } from 'ng2-alfresco-core';
|
||||
|
||||
declare var componentHandler: any;
|
||||
|
||||
@Component({
|
||||
selector: 'alfresco-setting-demo',
|
||||
templateUrl: './setting.component.html',
|
||||
styleUrls: ['./setting.component.css']
|
||||
})
|
||||
export class SettingComponent {
|
||||
export class SettingComponent implements AfterViewChecked {
|
||||
|
||||
ecmHost: string;
|
||||
bpmHost: string;
|
||||
@ -35,24 +37,39 @@ export class SettingComponent {
|
||||
this.bpmHost = this.settingsService.bpmHost;
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
// workaround for MDL issues with dynamic components
|
||||
if (componentHandler) {
|
||||
componentHandler.upgradeAllRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
public onChangeECMHost(event: KeyboardEvent): void {
|
||||
let value = (<HTMLInputElement>event.target).value.trim();
|
||||
if (value) {
|
||||
if (value && this.isValidUrl(value)) {
|
||||
this.logService.info(`ECM host: ${value}`);
|
||||
this.ecmHost = value;
|
||||
this.settingsService.ecmHost = value;
|
||||
this.storage.setItem(`ecmHost`, value);
|
||||
} else {
|
||||
console.error('Ecm address does not match the pattern');
|
||||
}
|
||||
}
|
||||
|
||||
public onChangeBPMHost(event: KeyboardEvent): void {
|
||||
let value = (<HTMLInputElement>event.target).value.trim();
|
||||
if (value) {
|
||||
if (value && this.isValidUrl(value)) {
|
||||
this.logService.info(`BPM host: ${value}`);
|
||||
this.bpmHost = value;
|
||||
this.settingsService.bpmHost = value;
|
||||
this.storage.setItem(`bpmHost`, value);
|
||||
} else {
|
||||
console.error('Bpm address does not match the pattern');
|
||||
}
|
||||
}
|
||||
|
||||
isValidUrl(url: string) {
|
||||
return /^(http|https):\/\/.*/.test(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
39
demo-shell-ng2/app/components/social/social.component.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/*!
|
||||
* @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 } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'alfresco-social-demo',
|
||||
template: `
|
||||
<label for="nodeId"><b>Insert Node Id</b></label><br>
|
||||
<input id="nodeId" type="text" size="48" [(ngModel)]="nodeId"><br>
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--4-col">
|
||||
Like component
|
||||
<adf-like [nodeId]="nodeId"></adf-like></div>
|
||||
<div class="mdl-cell mdl-cell--4-col">
|
||||
Rating component
|
||||
<adf-rating [nodeId]="nodeId"></adf-rating>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class SocialComponent {
|
||||
|
||||
nodeId: string = '74cd8a96-8a21-47e5-9b3b-a1b3e296787d';
|
||||
}
|
@ -25,6 +25,7 @@ import 'ng2-alfresco-documentlist';
|
||||
import 'ng2-alfresco-login';
|
||||
import 'ng2-alfresco-search';
|
||||
import 'ng2-alfresco-tag';
|
||||
import 'ng2-alfresco-social';
|
||||
import 'ng2-alfresco-upload';
|
||||
import 'ng2-alfresco-viewer';
|
||||
import 'ng2-alfresco-webscript';
|
||||
|
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "Alfresco-Angular2-Demo",
|
||||
"description": "Demo shell for Alfresco Angular2 components",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings dist",
|
||||
"start": "npm run server-versions && webpack-dev-server --progress --max_old_space_size=4096 --max_new_space_size=4096",
|
||||
"start": "npm run tslint && npm run server-versions && webpack-dev-server --progress --max_old_space_size=4096 --max_new_space_size=4096",
|
||||
"start:dist": "wsrv -s dist/ -p 3000 -a 0.0.0.0",
|
||||
"clean-build": "rimraf 'app/{,**/}**.js' 'app/{,**/}**.js.map' 'app/{,**/}**.d.ts'",
|
||||
"test": "karma start",
|
||||
@ -63,7 +63,7 @@
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"@angular/upgrade": "2.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"chart.js": "2.5.0",
|
||||
"core-js": "2.4.1",
|
||||
"dialog-polyfill": "0.4.7",
|
||||
@ -75,20 +75,21 @@
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"moment": "2.15.1",
|
||||
"ng2-3d-editor": "0.0.15",
|
||||
"ng2-activiti-analytics": "1.3.0",
|
||||
"ng2-activiti-form": "1.3.0",
|
||||
"ng2-activiti-processlist": "1.3.0",
|
||||
"ng2-activiti-tasklist": "1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-datatable": "1.3.0",
|
||||
"ng2-alfresco-documentlist": "1.3.0",
|
||||
"ng2-alfresco-login": "1.3.0",
|
||||
"ng2-alfresco-search": "1.3.0",
|
||||
"ng2-alfresco-tag": "1.3.0",
|
||||
"ng2-alfresco-upload": "1.3.0",
|
||||
"ng2-alfresco-userinfo": "1.3.0",
|
||||
"ng2-alfresco-viewer": "1.3.0",
|
||||
"ng2-alfresco-webscript": "1.3.0",
|
||||
"ng2-activiti-analytics": "1.4.0",
|
||||
"ng2-activiti-form": "1.4.0",
|
||||
"ng2-activiti-processlist": "1.4.0",
|
||||
"ng2-activiti-tasklist": "1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-alfresco-datatable": "1.4.0",
|
||||
"ng2-alfresco-documentlist": "1.4.0",
|
||||
"ng2-alfresco-login": "1.4.0",
|
||||
"ng2-alfresco-search": "1.4.0",
|
||||
"ng2-alfresco-tag": "1.4.0",
|
||||
"ng2-alfresco-social": "1.3.0",
|
||||
"ng2-alfresco-upload": "1.4.0",
|
||||
"ng2-alfresco-userinfo": "1.4.0",
|
||||
"ng2-alfresco-viewer": "1.4.0",
|
||||
"ng2-alfresco-webscript": "1.4.0",
|
||||
"ng2-charts": "1.5.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"pdfjs-dist": "1.5.404",
|
||||
|
11
fabric8.yml
@ -1,11 +0,0 @@
|
||||
---
|
||||
buildName: "adf2"
|
||||
links:
|
||||
Git: "https://github.com/Alfresco/alfresco-ng2-components.git"
|
||||
Job: "http://192.168.64.3:31752/job/adf2"
|
||||
Production: "http://10.0.0.114:80/kubernetes/pods?namespace=default-production"
|
||||
Staging: "http://10.0.0.114:80/kubernetes/pods?namespace=default-staging"
|
||||
environments:
|
||||
Staging: "default-staging"
|
||||
Production: "default-production"
|
||||
useLocalFlow: true
|
@ -59,10 +59,10 @@
|
||||
"moment": "2.15.1",
|
||||
"raphael": "^2.2.6",
|
||||
"ng2-translate": "2.5.0",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-activiti-diagrams": "1.3.0",
|
||||
"ng2-activiti-analytics": "1.3.0"
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-activiti-diagrams": "1.4.0",
|
||||
"ng2-activiti-analytics": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.33",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-analytics",
|
||||
"description": "Activiti Angular2 Analytics Component",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
|
||||
@ -52,14 +52,14 @@
|
||||
"@angular/platform-browser": "2.2.2",
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"chart.js": "2.5.0",
|
||||
"core-js": "2.4.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"moment": "2.15.1",
|
||||
"ng2-activiti-diagrams": "1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-activiti-diagrams": "1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-charts": "1.5.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"raphael": "2.2.7",
|
||||
|
@ -428,6 +428,7 @@ describe('AnalyticsReportParametersComponent', () => {
|
||||
});
|
||||
|
||||
describe('When the form is rendered correctly', () => {
|
||||
let validForm: boolean = true;
|
||||
let values: any = {
|
||||
dateRange: {
|
||||
startDate: '2016-09-01', endDate: '2016-10-05'
|
||||
@ -468,11 +469,17 @@ describe('AnalyticsReportParametersComponent', () => {
|
||||
fixture.whenStable().then(() => {
|
||||
component.toggleParameters();
|
||||
component.reportId = '1';
|
||||
spyOn(component, 'isFormValid').and.returnValue(true);
|
||||
spyOn(component, 'isFormValid').and.callFake(() => {
|
||||
return validForm;
|
||||
});
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
validForm = true;
|
||||
});
|
||||
|
||||
it('Should be able to change the report title', async(() => {
|
||||
let title: HTMLElement = element.querySelector('h4');
|
||||
title.click();
|
||||
@ -567,6 +574,52 @@ describe('AnalyticsReportParametersComponent', () => {
|
||||
contentType: 'json'
|
||||
});
|
||||
}));
|
||||
|
||||
it('Should hide export button if the form is not valid', async(() => {
|
||||
let exportButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#export-button');
|
||||
expect(exportButton).toBeDefined();
|
||||
expect(exportButton).not.toBeNull();
|
||||
validForm = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
exportButton = <HTMLButtonElement>element.querySelector('#export-button');
|
||||
expect(exportButton).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('Should hide save button if the form is not valid', async(() => {
|
||||
let saveButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#save-button');
|
||||
expect(saveButton).toBeDefined();
|
||||
expect(saveButton).not.toBeNull();
|
||||
validForm = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
saveButton = <HTMLButtonElement>element.querySelector('#save-button');
|
||||
expect(saveButton).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('Should show export and save button when the form became valid', async(() => {
|
||||
validForm = false;
|
||||
fixture.detectChanges();
|
||||
let saveButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#save-button');
|
||||
let exportButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#export-button');
|
||||
expect(saveButton).toBeNull();
|
||||
expect(exportButton).toBeNull();
|
||||
validForm = true;
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
saveButton = <HTMLButtonElement>element.querySelector('#save-button');
|
||||
exportButton = <HTMLButtonElement>element.querySelector('#export-button');
|
||||
expect(saveButton).not.toBeNull();
|
||||
expect(saveButton).toBeDefined();
|
||||
expect(exportButton).not.toBeNull();
|
||||
expect(exportButton).toBeDefined();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -25,9 +25,10 @@ import {
|
||||
SimpleChanges,
|
||||
OnDestroy,
|
||||
AfterViewChecked,
|
||||
AfterContentChecked,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { FormGroup, FormBuilder, FormControl } from '@angular/forms';
|
||||
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
|
||||
import * as moment from 'moment';
|
||||
import { AlfrescoTranslationService, LogService, ContentService } from 'ng2-alfresco-core';
|
||||
import { AnalyticsService } from '../services/analytics.service';
|
||||
@ -47,7 +48,7 @@ declare let dialogPolyfill: any;
|
||||
templateUrl: './analytics-report-parameters.component.html',
|
||||
styleUrls: ['./analytics-report-parameters.component.css']
|
||||
})
|
||||
export class AnalyticsReportParametersComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked {
|
||||
export class AnalyticsReportParametersComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked, AfterContentChecked {
|
||||
|
||||
public static FORMAT_DATE_ACTIVITI: string = 'YYYY-MM-DD';
|
||||
|
||||
@ -102,6 +103,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
|
||||
private reportParamQuery: ReportQuery;
|
||||
private reportName: string;
|
||||
private hideParameters: boolean = true;
|
||||
private formValidState: boolean = false;
|
||||
|
||||
constructor(private translateService: AlfrescoTranslationService,
|
||||
private analyticsService: AnalyticsService,
|
||||
@ -131,6 +133,9 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.isEditable = false;
|
||||
if (this.reportForm) {
|
||||
this.reportForm.reset();
|
||||
}
|
||||
let reportId = changes['reportId'];
|
||||
if (reportId && reportId.currentValue) {
|
||||
this.getReportParams(reportId.currentValue);
|
||||
@ -147,42 +152,42 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
|
||||
parameters.forEach((param: ReportParameterDetailsModel) => {
|
||||
switch (param.type) {
|
||||
case 'dateRange' :
|
||||
formBuilderGroup.dateRange = new FormGroup({});
|
||||
formBuilderGroup.dateRange = new FormGroup({}, Validators.required);
|
||||
break;
|
||||
case 'processDefinition':
|
||||
formBuilderGroup.processDefGroup = new FormGroup({
|
||||
processDefinitionId: new FormControl()
|
||||
});
|
||||
processDefinitionId: new FormControl(null, Validators.required, null)
|
||||
}, Validators.required);
|
||||
break;
|
||||
case 'duration':
|
||||
formBuilderGroup.durationGroup = new FormGroup({
|
||||
duration: new FormControl()
|
||||
});
|
||||
duration: new FormControl(null, Validators.required, null)
|
||||
}, Validators.required);
|
||||
break;
|
||||
case 'dateInterval':
|
||||
formBuilderGroup.dateIntervalGroup = new FormGroup({
|
||||
dateRangeInterval: new FormControl()
|
||||
});
|
||||
dateRangeInterval: new FormControl(null, Validators.required, null)
|
||||
}, Validators.required);
|
||||
break;
|
||||
case 'boolean':
|
||||
formBuilderGroup.typeFilteringGroup = new FormGroup({
|
||||
typeFiltering: new FormControl()
|
||||
});
|
||||
typeFiltering: new FormControl(null, Validators.required, null)
|
||||
}, Validators.required);
|
||||
break;
|
||||
case 'task':
|
||||
formBuilderGroup.taskGroup = new FormGroup({
|
||||
taskName: new FormControl()
|
||||
});
|
||||
taskName: new FormControl(null, Validators.required, null)
|
||||
}, Validators.required);
|
||||
break;
|
||||
case 'integer':
|
||||
formBuilderGroup.processInstanceGroup = new FormGroup({
|
||||
slowProcessInstanceInteger: new FormControl()
|
||||
});
|
||||
slowProcessInstanceInteger: new FormControl(null, Validators.required, null)
|
||||
}, Validators.required);
|
||||
break;
|
||||
case 'status':
|
||||
formBuilderGroup.statusGroup = new FormGroup({
|
||||
status: new FormControl()
|
||||
});
|
||||
status: new FormControl(null, Validators.required, null)
|
||||
}, Validators.required);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
@ -190,6 +195,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
|
||||
});
|
||||
this.reportForm = this.formBuilder.group(formBuilderGroup);
|
||||
this.reportForm.valueChanges.subscribe(data => this.onValueChanged(data));
|
||||
this.reportForm.statusChanges.subscribe(data => this.onStatusChanged(data));
|
||||
}
|
||||
|
||||
public getReportParams(reportId: string) {
|
||||
@ -243,6 +249,12 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged(status: any) {
|
||||
if (this.reportForm && !this.reportForm.pending && this.reportForm.dirty) {
|
||||
this.formValidState = this.reportForm.valid;
|
||||
}
|
||||
}
|
||||
|
||||
public convertMomentDate(date: string) {
|
||||
return moment(date, AnalyticsReportParametersComponent.FORMAT_DATE_ACTIVITI, true)
|
||||
.format(AnalyticsReportParametersComponent.FORMAT_DATE_ACTIVITI) + 'T00:00:00.000Z';
|
||||
@ -346,14 +358,14 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
|
||||
this.reportName = '';
|
||||
}
|
||||
|
||||
isFormValid() {
|
||||
return this.reportForm && this.reportForm.valid && this.reportForm.dirty;
|
||||
}
|
||||
|
||||
isSaveAction() {
|
||||
return this.action === 'Save';
|
||||
}
|
||||
|
||||
isFormValid() {
|
||||
return this.reportForm && this.reportForm.dirty && this.reportForm.valid;
|
||||
}
|
||||
|
||||
doExport(paramQuery: ReportQuery) {
|
||||
this.analyticsService.exportReportToCsv(this.reportId, paramQuery).subscribe(
|
||||
(data: any) => {
|
||||
@ -375,12 +387,17 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
// workaround for MDL issues with dynamic components
|
||||
if (componentHandler) {
|
||||
componentHandler.upgradeAllRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (this.reportForm && this.reportForm.valid) {
|
||||
this.reportForm.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
toggleParameters() {
|
||||
this.hideParameters = !this.hideParameters;
|
||||
}
|
||||
|
@ -43,15 +43,15 @@
|
||||
"@angular/material": "2.0.0-beta.1",
|
||||
"@angular/router": "3.2.2",
|
||||
"@angular/upgrade": "2.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"core-js": "2.4.1",
|
||||
"dialog-polyfill": "0.4.7",
|
||||
"element.scrollintoviewifneeded-polyfill": "1.0.1",
|
||||
"intl": "1.2.4",
|
||||
"material-design-icons": "2.2.3",
|
||||
"material-design-lite": "1.2.1",
|
||||
"ng2-activiti-diagrams": "1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-activiti-diagrams": "1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"raphael": "^2.2.6",
|
||||
"reflect-metadata": "0.1.10",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-diagrams",
|
||||
"description": "Activiti Angular2 Diagrams Component",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
|
||||
@ -48,10 +48,10 @@
|
||||
"@angular/platform-browser": "2.2.2",
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"core-js": "2.4.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"raphael": "^2.2.6",
|
||||
"reflect-metadata": "0.1.10",
|
||||
|
@ -438,10 +438,33 @@ class MyComponent {
|
||||
| getRestFieldValuesByProcessId | (processDefinitionId: string, field: string) | Observable\<any\> | |
|
||||
| getRestFieldValuesColumnByProcessId | (processDefinitionId: string, field: string, column?: string) | Observable\<any\> | |
|
||||
| getRestFieldValuesColumn | (taskId: string, field: string, column?: string) | Observable\<any\> | |
|
||||
| getWorkflowGroups\* | (filter: string, groupId?: string) | Observable\<GroupModel[]\> | |
|
||||
| getWorkflowUsers\* | (filter: string, groupId?: string) | Observable\<GroupUserModel[]\> | |
|
||||
| getWorkflowGroups\ | (filter: string, groupId?: string) | Observable\<GroupModel[]\> | |
|
||||
| getWorkflowUsers\ | (filter: string, groupId?: string) | Observable\<GroupUserModel[]\> | |
|
||||
|
||||
\* _Uses private Activiti WebApp api_
|
||||
## Common scenarios
|
||||
|
||||
### Changing field value based on another field
|
||||
|
||||
Create a simple Form with a dropdown widget (id: `type`), and a multiline text (id: `description`).
|
||||
|
||||
```ts
|
||||
formService.formFieldValueChanged.subscribe((e: FormFieldEvent) => {
|
||||
if (e.field.id === 'type') {
|
||||
const fields: FormFieldModel[] = e.form.getFormFields();
|
||||
const description = fields.find(f => f.id === 'description');
|
||||
if (description != null) {
|
||||
console.log(description);
|
||||
description.value = 'Type set to ' + e.field.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You subscribe to the `formFieldValueChanged` event and check whether event is raised for the `type` widget, then you search for a `description` widget and assign its value to some simple text.
|
||||
|
||||
The result should be as following:
|
||||
|
||||

|
||||
|
||||
## See also
|
||||
|
||||
|
@ -56,9 +56,9 @@
|
||||
"moment": "2.15.1",
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-activiti-form": "1.3.0"
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-activiti-form": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.33",
|
||||
|
After Width: | Height: | Size: 29 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-form",
|
||||
"description": "Alfresco Activiti Form Component for Angular 2",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
|
||||
@ -55,12 +55,12 @@
|
||||
"@angular/platform-browser": "2.2.2",
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"core-js": "2.4.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"moment": "2.15.1",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"reflect-metadata": "0.1.10",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
|
@ -26,7 +26,6 @@ import { FormEvent, FormErrorEvent } from './../events/index';
|
||||
|
||||
import { WidgetVisibilityService } from './../services/widget-visibility.service';
|
||||
|
||||
declare let dialogPolyfill: any;
|
||||
declare var componentHandler: any;
|
||||
|
||||
/**
|
||||
@ -118,7 +117,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
|
||||
showSaveButton: boolean = true;
|
||||
|
||||
@Input()
|
||||
showDebugButton: boolean = true;
|
||||
showDebugButton: boolean = false;
|
||||
|
||||
@Input()
|
||||
readOnly: boolean = false;
|
||||
|
@ -121,6 +121,109 @@ describe('FormFieldModel', () => {
|
||||
expect(field.value).toBe('deferred');
|
||||
});
|
||||
|
||||
it('should parse the date with the default format (D-M-YYYY) if the display format is missing', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(field.value).toBe('28-4-2017');
|
||||
expect(form.values['mmddyyyy']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should parse the date with the format MM-DD-YYYY', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'mmddyyyy',
|
||||
name: 'MM-DD-YYYY',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
dateDisplayFormat: 'MM-DD-YYYY'
|
||||
});
|
||||
expect(field.value).toBe('04-28-2017');
|
||||
expect(form.values['mmddyyyy']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should parse the date with the format MM-YY-DD', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'mmyydd',
|
||||
name: 'MM-YY-DD',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'mmyydd',
|
||||
name: 'MM-YY-DD',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
dateDisplayFormat: 'MM-YY-DD'
|
||||
});
|
||||
expect(field.value).toBe('04-17-28');
|
||||
expect(form.values['mmyydd']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should parse the date with the format DD-MM-YYYY', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
fieldType: 'FormFieldRepresentation',
|
||||
id: 'ddmmyyy',
|
||||
name: 'DD-MM-YYYY',
|
||||
type: 'date',
|
||||
value: '2017-04-28T00:00:00.000+0000',
|
||||
required: false,
|
||||
readOnly: false,
|
||||
params: {
|
||||
field: {
|
||||
id: 'ddmmyyy',
|
||||
name: 'DD-MM-YYYY',
|
||||
type: 'date',
|
||||
value: null,
|
||||
required: false,
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
dateDisplayFormat: 'DD-MM-YYYY'
|
||||
});
|
||||
expect(field.value).toBe('28-04-2017');
|
||||
expect(form.values['ddmmyyy']).toEqual('2017-04-28T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should return the label of selected dropdown value ', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
|
@ -76,7 +76,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
visibilityCondition: WidgetVisibilityModel = null;
|
||||
enableFractions: boolean = false;
|
||||
currency: string = null;
|
||||
dateDisplayFormat: string = this.defaultDateFormat;
|
||||
dateDisplayFormat: string = this.dateDisplayFormat || this.defaultDateFormat;
|
||||
|
||||
// container model members
|
||||
numberOfColumns: number = 1;
|
||||
@ -249,9 +249,14 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
*/
|
||||
if (json.type === FormFieldTypes.DATE) {
|
||||
if (value) {
|
||||
let d = moment(value.split('T')[0], 'YYYY-M-D');
|
||||
if (d.isValid()) {
|
||||
value = d.format(this.dateDisplayFormat);
|
||||
let dateValue;
|
||||
if (NumberFieldValidator.isNumber(value)) {
|
||||
dateValue = moment(value);
|
||||
} else {
|
||||
dateValue = moment(value.split('T')[0], 'YYYY-M-D');
|
||||
}
|
||||
if (dateValue && dateValue.isValid()) {
|
||||
value = dateValue.format(this.dateDisplayFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,9 +312,14 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
}
|
||||
break;
|
||||
case FormFieldTypes.DATE:
|
||||
let d = moment(this.value, this.dateDisplayFormat);
|
||||
if (d.isValid()) {
|
||||
this.form.values[this.id] = `${d.format('YYYY-MM-DD')}T00:00:00.000Z`;
|
||||
let dateValue;
|
||||
if (NumberFieldValidator.isNumber(this.value)) {
|
||||
dateValue = moment(this.value);
|
||||
} else {
|
||||
dateValue = moment(this.value, this.dateDisplayFormat);
|
||||
}
|
||||
if (dateValue && dateValue.isValid()) {
|
||||
this.form.values[this.id] = `${dateValue.format('YYYY-MM-DD')}T00:00:00.000Z`;
|
||||
} else {
|
||||
this.form.values[this.id] = null;
|
||||
}
|
||||
|
@ -42,30 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="'dynamic-table'">
|
||||
|
||||
<div class="display-value-widget__dynamic-table">
|
||||
<div>{{field.name}}</div>
|
||||
<div class="display-value-dynamic-table-widget__table-container">
|
||||
<table class="mdl-data-table mdl-js-data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let column of visibleColumns"
|
||||
class="mdl-data-table__cell--non-numeric is-disabled">
|
||||
{{column.name}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let row of rows">
|
||||
<td *ngFor="let column of visibleColumns"
|
||||
class="mdl-data-table__cell--non-numeric is-disabled">
|
||||
{{ getCellValue(row, column) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<dynamic-table-widget [field]="field" [readOnly]="!tableEditable"></dynamic-table-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'upload'">
|
||||
<div *ngIf="hasFile" class="mdl-grid">
|
||||
|
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { CoreModule, LogServiceMock } from 'ng2-alfresco-core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
@ -25,7 +26,6 @@ import { EcmModelService } from '../../../services/ecm-model.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { DynamicTableColumn, DynamicTableRow } from './../dynamic-table/dynamic-table.widget.model';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
describe('DisplayValueWidget', () => {
|
||||
@ -441,6 +441,65 @@ describe('DisplayValueWidget', () => {
|
||||
expect(widget.value).toBe('<invalid value>');
|
||||
});
|
||||
|
||||
it('should show the [DATE] field with the default format (D-M-YYYY) if the display format is missing', () => {
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
value: '1982-03-13T00:00:00.000Z',
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DATE
|
||||
}
|
||||
}
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.value).toBe('13-3-1982');
|
||||
});
|
||||
|
||||
it('should show the [DATE] field with the custom display format (MM-DD-YYYY)', () => {
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
value: '1982-03-13T00:00:00.000Z',
|
||||
dateDisplayFormat: 'MM-DD-YYYY',
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DATE
|
||||
}
|
||||
}
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.value).toBe('03-13-1982');
|
||||
});
|
||||
|
||||
it('should show the [DATE] field with the custom display format (MM-YY-DD)', () => {
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
value: '1982-03-13T00:00:00.000Z',
|
||||
dateDisplayFormat: 'MM-YY-DD',
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DATE
|
||||
}
|
||||
}
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.value).toBe('03-82-13');
|
||||
});
|
||||
|
||||
it('should show the [DATE] field with the custom display format (DD-MM-YYYY)', () => {
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
value: '1982-03-13T00:00:00.000Z',
|
||||
dateDisplayFormat: 'DD-MM-YYYY',
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DATE
|
||||
}
|
||||
}
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.value).toBe('13-03-1982');
|
||||
});
|
||||
|
||||
it('should not setup [DATE] field when missing value', () => {
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
@ -549,135 +608,6 @@ describe('DisplayValueWidget', () => {
|
||||
expect(widget.value).toBe(value);
|
||||
});
|
||||
|
||||
it('should setup [DYNAMIC_TABLE] field', () => {
|
||||
let columns = [{id: '1', visible: false}, {id: '2', visible: true}];
|
||||
let rows = [{}, {}];
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DYNAMIC_TABLE
|
||||
}
|
||||
},
|
||||
columnDefinitions: columns,
|
||||
value: rows
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(widget.columns.length).toBe(2);
|
||||
expect(widget.columns[0].id).toBe(columns[0].id);
|
||||
expect(widget.columns[1].id).toBe(columns[1].id);
|
||||
|
||||
expect(widget.visibleColumns.length).toBe(1);
|
||||
expect(widget.visibleColumns[0].id).toBe(columns[1].id);
|
||||
|
||||
expect(widget.rows.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should setup [DYNAMIC_TABLE] field with empty schema', () => {
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.DYNAMIC_TABLE
|
||||
}
|
||||
},
|
||||
columnDefinitions: null,
|
||||
value: null
|
||||
});
|
||||
widget.ngOnInit();
|
||||
|
||||
expect(widget.value).toBeNull();
|
||||
expect(widget.columns).toEqual([]);
|
||||
expect(widget.rows).toEqual([]);
|
||||
});
|
||||
|
||||
it('should retrieve default cell value', () => {
|
||||
const value = '<value>';
|
||||
let row = <DynamicTableRow> {value: {key: value}};
|
||||
let column = <DynamicTableColumn> {id: 'key'};
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe(value);
|
||||
});
|
||||
|
||||
it('should retrieve dropdown cell value', () => {
|
||||
const value = {id: '1', name: 'one'};
|
||||
let row = <DynamicTableRow> {value: {key: value}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Dropdown'};
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe(value.name);
|
||||
});
|
||||
|
||||
it('should fallback to empty cell value for dropdown', () => {
|
||||
let row = <DynamicTableRow> {value: {}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Dropdown'};
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('');
|
||||
});
|
||||
|
||||
it('should retrieve boolean cell value', () => {
|
||||
let row1 = <DynamicTableRow> {value: {key: true}};
|
||||
let row2 = <DynamicTableRow> {value: {key: 'positive'}};
|
||||
let row3 = <DynamicTableRow> {value: {key: null}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Boolean'};
|
||||
|
||||
expect(widget.getCellValue(row1, column)).toBe(true);
|
||||
expect(widget.getCellValue(row2, column)).toBe(true);
|
||||
expect(widget.getCellValue(row3, column)).toBe(false);
|
||||
});
|
||||
|
||||
it('should retrieve date cell value', () => {
|
||||
const value = '2016-10-04T00:00:00.000Z';
|
||||
let row = <DynamicTableRow> {value: {key: value}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Date'};
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('4-10-2016');
|
||||
});
|
||||
|
||||
it('should fallback to empty cell value for date', () => {
|
||||
let row = <DynamicTableRow> {value: {}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Date'};
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('');
|
||||
});
|
||||
|
||||
it('should retrieve empty text cell value', () => {
|
||||
let row = <DynamicTableRow> {value: {}};
|
||||
let column = <DynamicTableColumn> {id: 'key'};
|
||||
|
||||
expect(widget.getCellValue(row, column)).toBe('');
|
||||
});
|
||||
|
||||
it('should prepend default amount currency', () => {
|
||||
const value = '10';
|
||||
let row = <DynamicTableRow> {value: {key: value}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Amount'};
|
||||
|
||||
const expected = `$ ${value}`;
|
||||
expect(widget.getCellValue(row, column)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should prepend custom amount currency', () => {
|
||||
const value = '10';
|
||||
const currency = 'GBP';
|
||||
let row = <DynamicTableRow> {value: {key: value}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Amount', amountCurrency: currency};
|
||||
|
||||
const expected = `${currency} ${value}`;
|
||||
expect(widget.getCellValue(row, column)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should use zero for missing amount', () => {
|
||||
const value = null;
|
||||
const currency = 'GBP';
|
||||
let row = <DynamicTableRow> {value: {key: value}};
|
||||
let column = <DynamicTableColumn> {id: 'key', type: 'Amount', amountCurrency: currency};
|
||||
|
||||
const expected = `${currency} 0`;
|
||||
expect(widget.getCellValue(row, column)).toBe(expected);
|
||||
});
|
||||
|
||||
describe('UI check', () => {
|
||||
let widgetUI: DisplayValueWidget;
|
||||
let fixture: ComponentFixture<DisplayValueWidget>;
|
||||
@ -689,12 +619,16 @@ describe('DisplayValueWidget', () => {
|
||||
window['componentHandler'] = componentHandler;
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [DisplayValueWidget, ActivitiContent],
|
||||
declarations: [
|
||||
DisplayValueWidget,
|
||||
ActivitiContent
|
||||
],
|
||||
providers: [
|
||||
EcmModelService,
|
||||
FormService,
|
||||
WidgetVisibilityService
|
||||
]
|
||||
],
|
||||
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DisplayValueWidget);
|
||||
widgetUI = fixture.componentInstance;
|
||||
|
@ -22,8 +22,8 @@ import { WidgetComponent } from './../widget.component';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { FormFieldOption } from './../core/form-field-option';
|
||||
import { DynamicTableColumn, DynamicTableRow } from './../dynamic-table/dynamic-table.widget.model';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { NumberFieldValidator } from '../core/form-field-validator';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@ -42,9 +42,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
linkText: string;
|
||||
|
||||
// dynamic table
|
||||
rows: DynamicTableRow[] = [];
|
||||
columns: DynamicTableColumn[] = [];
|
||||
visibleColumns: DynamicTableColumn[] = [];
|
||||
tableEditable = false;
|
||||
|
||||
// upload/attach
|
||||
hasFile: boolean = false;
|
||||
@ -64,6 +62,10 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
if (this.field.params['showDocumentContent'] !== undefined) {
|
||||
this.showDocumentContent = !!this.field.params['showDocumentContent'];
|
||||
}
|
||||
if (this.field.params['tableEditable'] !== undefined) {
|
||||
this.tableEditable = !!this.field.params['tableEditable'];
|
||||
}
|
||||
|
||||
let originalField = this.field.params['field'];
|
||||
if (originalField && originalField.type) {
|
||||
this.fieldType = originalField.type;
|
||||
@ -115,10 +117,15 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
break;
|
||||
case FormFieldTypes.DATE:
|
||||
if (this.value) {
|
||||
let d = moment(this.value.split('T')[0], 'YYYY-M-D');
|
||||
if (d.isValid()) {
|
||||
const displayFormat = originalField['dateDisplayFormat'] || this.field.defaultDateFormat;
|
||||
this.value = d.format(displayFormat);
|
||||
let dateValue;
|
||||
if (NumberFieldValidator.isNumber(this.value)) {
|
||||
dateValue = moment(this.value);
|
||||
} else {
|
||||
dateValue = moment(this.value.split('T')[0], 'YYYY-M-D');
|
||||
}
|
||||
if (dateValue && dateValue.isValid()) {
|
||||
const displayFormat = this.field.dateDisplayFormat || this.field.defaultDateFormat;
|
||||
this.value = dateValue.format(displayFormat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -132,16 +139,6 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
this.linkUrl = this.getHyperlinkUrl(this.field);
|
||||
this.linkText = this.getHyperlinkText(this.field);
|
||||
break;
|
||||
case FormFieldTypes.DYNAMIC_TABLE:
|
||||
let json = this.field.json;
|
||||
if (json.columnDefinitions) {
|
||||
this.columns = json.columnDefinitions.map(obj => <DynamicTableColumn> obj);
|
||||
this.visibleColumns = this.columns.filter(col => col.visible);
|
||||
}
|
||||
if (json.value) {
|
||||
this.rows = json.value.map(obj => <DynamicTableRow> {selected: false, value: obj});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.value = this.field.value;
|
||||
break;
|
||||
@ -216,31 +213,4 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any {
|
||||
|
||||
let result = row.value[column.id];
|
||||
|
||||
if (column.type === 'Dropdown') {
|
||||
if (result) {
|
||||
return result.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (column.type === 'Boolean') {
|
||||
return result ? true : false;
|
||||
}
|
||||
|
||||
if (column.type === 'Date') {
|
||||
if (result) {
|
||||
return moment(result.split('T')[0], 'YYYY-MM-DD').format('D-M-YYYY');
|
||||
}
|
||||
}
|
||||
|
||||
if (column.type === 'Amount') {
|
||||
return (column.amountCurrency || '$') + ' ' + (result || 0);
|
||||
}
|
||||
|
||||
return result || '';
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="dynamic-table-widget__buttons">
|
||||
<div class="dynamic-table-widget__buttons" *ngIf="!readOnly">
|
||||
<button class="mdl-button mdl-js-button mdl-button--icon"
|
||||
[disabled]="!hasSelection()"
|
||||
(click)="moveSelectionUp()">
|
||||
|
@ -54,9 +54,9 @@ export class DynamicTableModel extends FormWidgetModel {
|
||||
this.field = field;
|
||||
|
||||
if (field.json) {
|
||||
|
||||
if (field.json.columnDefinitions) {
|
||||
this.columns = field.json.columnDefinitions.map(obj => <DynamicTableColumn> obj);
|
||||
const columns = this.getColumns(field);
|
||||
if (columns) {
|
||||
this.columns = columns;
|
||||
this.visibleColumns = this.columns.filter(col => col.visible);
|
||||
}
|
||||
|
||||
@ -72,6 +72,20 @@ export class DynamicTableModel extends FormWidgetModel {
|
||||
];
|
||||
}
|
||||
|
||||
private getColumns(field: FormFieldModel): DynamicTableColumn[] {
|
||||
if (field && field.json) {
|
||||
let definitions = field.json.columnDefinitions;
|
||||
if (!definitions && field.json.params && field.json.params.field) {
|
||||
definitions = field.json.params.field.columnDefinitions;
|
||||
}
|
||||
|
||||
if (definitions) {
|
||||
return definitions.map(obj => <DynamicTableColumn> obj);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
flushValue() {
|
||||
if (this.field) {
|
||||
this.field.value = this.rows.map(r => r.value);
|
||||
|
@ -15,11 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ElementRef, OnInit } from '@angular/core';
|
||||
import { Component, ElementRef, OnInit, Input } from '@angular/core';
|
||||
import { LogService } from 'ng2-alfresco-core';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './dynamic-table.widget.model';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@ -31,6 +32,12 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
|
||||
|
||||
ERROR_MODEL_NOT_FOUND = 'Table model not found';
|
||||
|
||||
@Input()
|
||||
field: FormFieldModel;
|
||||
|
||||
@Input()
|
||||
readOnly: boolean = false;
|
||||
|
||||
content: DynamicTableModel;
|
||||
|
||||
editMode: boolean = false;
|
||||
@ -70,7 +77,7 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
|
||||
moveSelectionUp(): boolean {
|
||||
if (this.content) {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.content.moveRow(this.content.selectedRow, -1);
|
||||
return true;
|
||||
}
|
||||
@ -78,7 +85,7 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
|
||||
moveSelectionDown(): boolean {
|
||||
if (this.content) {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.content.moveRow(this.content.selectedRow, 1);
|
||||
return true;
|
||||
}
|
||||
@ -86,7 +93,7 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
|
||||
deleteSelection(): boolean {
|
||||
if (this.content) {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.content.deleteRow(this.content.selectedRow);
|
||||
return true;
|
||||
}
|
||||
@ -94,7 +101,7 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
|
||||
addNewRow(): boolean {
|
||||
if (this.content) {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.editRow = <DynamicTableRow> {
|
||||
isNew: true,
|
||||
selected: false,
|
||||
@ -107,7 +114,7 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
|
||||
editSelection(): boolean {
|
||||
if (this.content) {
|
||||
if (this.content && !this.readOnly) {
|
||||
this.editRow = this.copyRow(this.content.selectedRow);
|
||||
this.editMode = true;
|
||||
return true;
|
||||
|
@ -15,25 +15,48 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
StorageService,
|
||||
LogService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { CoreModule, AlfrescoApiService, LogService, LogServiceMock } from 'ng2-alfresco-core';
|
||||
import { FormService } from './form.service';
|
||||
import { Response, ResponseOptions } from '@angular/http';
|
||||
import { EcmModelService } from './ecm-model.service';
|
||||
import { FormDefinitionModel } from '../models/form-definition.model';
|
||||
import { Response, ResponseOptions } from '@angular/http';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('FormService', () => {
|
||||
let fakeGroupResponse = {
|
||||
'size': 2,
|
||||
'total': 2,
|
||||
'start': 0,
|
||||
'data': [{
|
||||
'id': 2004,
|
||||
'name': 'PEOPLE_GROUP',
|
||||
'externalId': null,
|
||||
'status': 'active',
|
||||
'groups': null
|
||||
}, { 'id': 2005, 'name': 'PEOPLE_GROUP_2', 'externalId': null, 'status': 'active', 'groups': null }]
|
||||
};
|
||||
|
||||
let responseBody: any;
|
||||
let service: FormService;
|
||||
let apiService: AlfrescoApiService;
|
||||
let logService: LogService;
|
||||
let bpmCli: any;
|
||||
let fakePeopleResponse = {
|
||||
'size': 3,
|
||||
'total': 3,
|
||||
'start': 0,
|
||||
'data': [{ 'id': 2002, 'firstName': 'Peo', 'lastName': 'Ple', 'email': 'people' }, {
|
||||
'id': 2003,
|
||||
'firstName': 'Peo02',
|
||||
'lastName': 'Ple02',
|
||||
'email': 'people02'
|
||||
}, { 'id': 2004, 'firstName': 'Peo03', 'lastName': 'Ple03', 'email': 'people03' }]
|
||||
};
|
||||
|
||||
function createFakeBlob() {
|
||||
function createFakeBlob() {
|
||||
let data = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
||||
|
||||
let bytes = new Uint8Array(data.length / 2);
|
||||
@ -41,24 +64,29 @@ describe('FormService', () => {
|
||||
for (let i = 0; i < data.length; i += 2) {
|
||||
bytes[i / 2] = parseInt(data.substring(i, i + 2), /* base = */ 16);
|
||||
}
|
||||
return new Blob([bytes], {type: 'image/png'});
|
||||
}
|
||||
return new Blob([bytes], { type: 'image/png' });
|
||||
}
|
||||
|
||||
describe('Form service', () => {
|
||||
|
||||
let service, injector, apiService, logService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
AlfrescoAuthenticationService,
|
||||
EcmModelService,
|
||||
StorageService,
|
||||
FormService,
|
||||
{ provide: LogService, useClass: LogServiceMock }
|
||||
]
|
||||
LogService
|
||||
]);
|
||||
});
|
||||
service = TestBed.get(FormService);
|
||||
apiService = TestBed.get(AlfrescoApiService);
|
||||
bpmCli = apiService.getInstance().bpmAuth;
|
||||
logService = TestBed.get(LogService);
|
||||
|
||||
beforeEach(() => {
|
||||
service = injector.get(FormService);
|
||||
apiService = injector.get(AlfrescoApiService);
|
||||
logService = injector.get(LogService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@ -69,14 +97,51 @@ describe('FormService', () => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should fetch and parse process definitions', (done) => {
|
||||
responseBody = {
|
||||
describe('Content tests', () => {
|
||||
|
||||
let responseBody = {
|
||||
data: [
|
||||
{id: '1'},
|
||||
{id: '2'}
|
||||
{ id: '1' },
|
||||
{ id: '2' }
|
||||
]
|
||||
};
|
||||
|
||||
let values = {
|
||||
field1: 'one',
|
||||
field2: 'two'
|
||||
};
|
||||
|
||||
let simpleResponseBody = { id: 1, modelType: 'test' };
|
||||
|
||||
let fileContentPdfResponseBody = {
|
||||
id: 999,
|
||||
name: 'fake-name.pdf',
|
||||
created: '2017-01-23T12:12:53.219+0000',
|
||||
createdBy: { id: 2, firstName: 'fake-admin', lastName: 'fake-last', 'email': 'fake-admin' },
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
};
|
||||
|
||||
let fileContentJpgResponseBody = {
|
||||
id: 888,
|
||||
name: 'fake-name.jpg',
|
||||
created: '2017-01-23T12:12:53.219+0000',
|
||||
createdBy: { id: 2, firstName: 'fake-admin', lastName: 'fake-last', 'email': 'fake-admin' },
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/jpeg',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'unsupported',
|
||||
thumbnailStatus: 'unsupported'
|
||||
};
|
||||
|
||||
it('should fetch and parse process definitions', (done) => {
|
||||
service.getProcessDefinitions().subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/process-definitions')).toBeTruthy();
|
||||
expect(result).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data);
|
||||
@ -91,13 +156,6 @@ describe('FormService', () => {
|
||||
});
|
||||
|
||||
it('should fetch and parse tasks', (done) => {
|
||||
responseBody = {
|
||||
data: [
|
||||
{id: '1'},
|
||||
{id: '2'}
|
||||
]
|
||||
};
|
||||
|
||||
service.getTasks().subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/query')).toBeTruthy();
|
||||
expect(result).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data);
|
||||
@ -112,29 +170,20 @@ describe('FormService', () => {
|
||||
});
|
||||
|
||||
it('should fetch and parse the task by id', (done) => {
|
||||
responseBody = {
|
||||
id: '1'
|
||||
};
|
||||
|
||||
service.getTask('1').subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/1')).toBeTruthy();
|
||||
expect(result.id).toEqual(responseBody.id);
|
||||
expect(result.id).toEqual('1');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify({ id: '1' })
|
||||
});
|
||||
});
|
||||
|
||||
it('should save task form', (done) => {
|
||||
let values = {
|
||||
field1: 'one',
|
||||
field2: 'two'
|
||||
};
|
||||
|
||||
service.saveTaskForm('1', values).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1/save-form')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1);
|
||||
@ -150,11 +199,6 @@ describe('FormService', () => {
|
||||
});
|
||||
|
||||
it('should complete task form', (done) => {
|
||||
let values = {
|
||||
field1: 'one',
|
||||
field2: 'two'
|
||||
};
|
||||
|
||||
service.completeTaskForm('1', values).subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1);
|
||||
@ -170,11 +214,6 @@ describe('FormService', () => {
|
||||
});
|
||||
|
||||
it('should complete task form with a specific outcome', (done) => {
|
||||
let values = {
|
||||
field1: 'one',
|
||||
field2: 'two'
|
||||
};
|
||||
|
||||
service.completeTaskForm('1', values, 'custom').subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2);
|
||||
@ -191,43 +230,39 @@ describe('FormService', () => {
|
||||
});
|
||||
|
||||
it('should get task form by id', (done) => {
|
||||
responseBody = {id: 1};
|
||||
|
||||
service.getTaskForm('1').subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
|
||||
expect(result.id).toEqual(responseBody.id);
|
||||
expect(result.id).toEqual(1);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify({ id: 1 })
|
||||
});
|
||||
});
|
||||
|
||||
it('should get form definition by id', (done) => {
|
||||
responseBody = {id: 1};
|
||||
|
||||
service.getFormDefinitionById('1').subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/form-models/1')).toBeTruthy();
|
||||
expect(result.id).toEqual(responseBody.id);
|
||||
expect(result.id).toEqual(1);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify({ id: 1 })
|
||||
});
|
||||
});
|
||||
|
||||
it('should get form definition id by name', (done) => {
|
||||
const formName = 'form1';
|
||||
const formId = 1;
|
||||
responseBody = {
|
||||
let response = {
|
||||
data: [
|
||||
{id: formId}
|
||||
{ id: formId }
|
||||
]
|
||||
};
|
||||
|
||||
@ -240,7 +275,7 @@ describe('FormService', () => {
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify(response)
|
||||
});
|
||||
});
|
||||
|
||||
@ -261,16 +296,16 @@ describe('FormService', () => {
|
||||
});
|
||||
|
||||
it('should not get form id from response', () => {
|
||||
let response = new Response(new ResponseOptions({body: null}));
|
||||
let response = new Response(new ResponseOptions({ body: null }));
|
||||
expect(service.getFormId(response)).toBeNull();
|
||||
|
||||
response = new Response(new ResponseOptions({body: {}}));
|
||||
response = new Response(new ResponseOptions({ body: {} }));
|
||||
expect(service.getFormId(response)).toBeNull();
|
||||
|
||||
response = new Response(new ResponseOptions({body: {data: null}}));
|
||||
response = new Response(new ResponseOptions({ body: { data: null } }));
|
||||
expect(service.getFormId(response)).toBeNull();
|
||||
|
||||
response = new Response(new ResponseOptions({body: {data: []}}));
|
||||
response = new Response(new ResponseOptions({ body: { data: [] } }));
|
||||
expect(service.getFormId(response)).toBeNull();
|
||||
|
||||
expect(service.getFormId(null)).toBeNull();
|
||||
@ -279,62 +314,64 @@ describe('FormService', () => {
|
||||
it('should fallback to empty json array', () => {
|
||||
expect(service.toJsonArray(null)).toEqual([]);
|
||||
|
||||
let response = new Response(new ResponseOptions({body: {}}));
|
||||
let response = new Response(new ResponseOptions({ body: {} }));
|
||||
expect(service.toJsonArray(response)).toEqual([]);
|
||||
|
||||
response = new Response(new ResponseOptions({body: {data: null}}));
|
||||
response = new Response(new ResponseOptions({ body: { data: null } }));
|
||||
expect(service.toJsonArray(response)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle error with generic message', () => {
|
||||
spyOn(logService, 'error').and.stub();
|
||||
|
||||
service.handleError(null);
|
||||
expect(logService.error).toHaveBeenCalledWith(FormService.UNKNOWN_ERROR_MESSAGE);
|
||||
service.handleError(null).subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toBe(FormService.UNKNOWN_ERROR_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error with error message', () => {
|
||||
spyOn(logService, 'error').and.stub();
|
||||
|
||||
const message = '<error>';
|
||||
service.handleError({message: message});
|
||||
|
||||
expect(logService.error).toHaveBeenCalledWith(message);
|
||||
service.handleError({ message: message }).subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toBe(message);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error with detailed message', () => {
|
||||
spyOn(logService, 'error').and.stub();
|
||||
service.handleError({
|
||||
status: '400',
|
||||
statusText: 'Bad request'
|
||||
}).subscribe(
|
||||
() => {
|
||||
},
|
||||
(error) => {
|
||||
expect(error).toBe('400 - Bad request');
|
||||
});
|
||||
expect(logService.error).toHaveBeenCalledWith('400 - Bad request');
|
||||
});
|
||||
|
||||
it('should handle error with generic message', () => {
|
||||
spyOn(logService, 'error').and.stub();
|
||||
service.handleError({});
|
||||
expect(logService.error).toHaveBeenCalledWith(FormService.GENERIC_ERROR_MESSAGE);
|
||||
service.handleError({}).subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toBe(FormService.GENERIC_ERROR_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get all the forms with modelType=2', (done) => {
|
||||
responseBody = {};
|
||||
|
||||
service.getForms().subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy();
|
||||
expect(result).toEqual(responseBody);
|
||||
expect(result).toEqual({});
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('should search for Form with modelType=2', (done) => {
|
||||
responseBody = {data: [{id: 1, name: 'findme'}, {id: 2, name: 'testform'}]};
|
||||
let response = { data: [{ id: 1, name: 'findme' }, { id: 2, name: 'testform' }] };
|
||||
|
||||
service.searchFrom('findme').subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy();
|
||||
@ -346,13 +383,11 @@ describe('FormService', () => {
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify(response)
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a Form with modelType=2', (done) => {
|
||||
responseBody = {id: 1, modelType: 'test'};
|
||||
|
||||
service.createForm('testName').subscribe(result => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/models')).toBeTruthy();
|
||||
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).modelType).toEqual(2);
|
||||
@ -363,16 +398,14 @@ describe('FormService', () => {
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify(simpleResponseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should add form fields to a form', (done) => {
|
||||
responseBody = {id: 1, modelType: 'test'};
|
||||
|
||||
let formId = '100';
|
||||
let name = 'testName';
|
||||
let data = [{name: 'name'}, {name: 'email'}];
|
||||
let data = [{ name: 'name' }, { name: 'email' }];
|
||||
let formDefinitionModel = new FormDefinitionModel(formId, name, 'testUserName', '2016-09-05T14:41:19.049Z', data);
|
||||
|
||||
service.addFieldsToAForm(formId, formDefinitionModel).subscribe(result => {
|
||||
@ -384,25 +417,12 @@ describe('FormService', () => {
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify(simpleResponseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the unsupported content when the file is an image', (done) => {
|
||||
let contentId: number = 999;
|
||||
responseBody = {
|
||||
id: contentId,
|
||||
name: 'fake-name.jpg',
|
||||
created: '2017-01-23T12:12:53.219+0000',
|
||||
createdBy: {id: 2, firstName: 'fake-admin', lastName: 'fake-last', 'email': 'fake-admin'},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'image/jpeg',
|
||||
simpleType: 'image',
|
||||
previewStatus: 'unsupported',
|
||||
thumbnailStatus: 'unsupported'
|
||||
};
|
||||
let contentId: number = 888;
|
||||
|
||||
service.getFileContent(contentId).subscribe(result => {
|
||||
expect(result.id).toEqual(contentId);
|
||||
@ -415,25 +435,12 @@ describe('FormService', () => {
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify(fileContentJpgResponseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the supported content when the file is a pdf', (done) => {
|
||||
let contentId: number = 888;
|
||||
responseBody = {
|
||||
id: contentId,
|
||||
name: 'fake-name.pdf',
|
||||
created: '2017-01-23T12:12:53.219+0000',
|
||||
createdBy: {id: 2, firstName: 'fake-admin', lastName: 'fake-last', 'email': 'fake-admin'},
|
||||
relatedContent: false,
|
||||
contentAvailable: true,
|
||||
link: false,
|
||||
mimeType: 'application/pdf',
|
||||
simpleType: 'pdf',
|
||||
previewStatus: 'created',
|
||||
thumbnailStatus: 'created'
|
||||
};
|
||||
let contentId: number = 999;
|
||||
|
||||
service.getFileContent(contentId).subscribe(result => {
|
||||
expect(result.id).toEqual(contentId);
|
||||
@ -446,22 +453,20 @@ describe('FormService', () => {
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(responseBody)
|
||||
responseText: JSON.stringify(fileContentPdfResponseBody)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the raw content URL', () => {
|
||||
let contentId: number = 999;
|
||||
let contentRawUrl = service.getFileRawContentUrl(contentId);
|
||||
expect(contentRawUrl).toEqual(`${bpmCli.basePath}/api/enterprise/content/${contentId}/raw`);
|
||||
let contentUrl = service.getFileRawContentUrl(contentId);
|
||||
expect(contentUrl).toContain(`/api/enterprise/content/${contentId}/raw`);
|
||||
});
|
||||
|
||||
it('should return a Blob as thumbnail', (done) => {
|
||||
let contentId: number = 999;
|
||||
|
||||
let blob = createFakeBlob();
|
||||
spyOn(service, 'getContentThumbnailUrl').and.returnValue(Observable.of(blob));
|
||||
|
||||
service.getContentThumbnailUrl(contentId).subscribe(result => {
|
||||
expect(result).toEqual(jasmine.any(Blob));
|
||||
expect(result.size).toEqual(48);
|
||||
@ -470,12 +475,46 @@ describe('FormService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of people', (done) => {
|
||||
let fakeFilter: string = 'whatever';
|
||||
|
||||
service.getWorkflowUsers(fakeFilter).subscribe(result => {
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(3);
|
||||
expect(result[0].id).toBe(2002);
|
||||
expect(result[0].firstName).toBe('Peo');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakePeopleResponse)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return list of groups', (done) => {
|
||||
let fakeFilter: string = 'whatever';
|
||||
|
||||
service.getWorkflowGroups(fakeFilter).subscribe(result => {
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0].id).toBe(2004);
|
||||
expect(result[0].name).toBe('PEOPLE_GROUP');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGroupResponse)
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a Form form a Node', (done) => {
|
||||
|
||||
let nameForm = 'testNode';
|
||||
|
||||
responseBody = {id: 1, modelType: 'test'};
|
||||
|
||||
let formId = 100;
|
||||
|
||||
stubCreateForm();
|
||||
@ -496,7 +535,7 @@ describe('FormService', () => {
|
||||
status: 200,
|
||||
statusText: 'HTTP/1.1 200 OK',
|
||||
contentType: 'text/xml;charset=UTF-8',
|
||||
responseText: {id: formId, name: 'test', lastUpdatedByFullName: 'uset', lastUpdated: '12-12-2016'}
|
||||
responseText: { id: formId, name: 'test', lastUpdatedByFullName: 'uset', lastUpdated: '12-12-2016' }
|
||||
});
|
||||
}
|
||||
|
||||
@ -513,9 +552,9 @@ describe('FormService', () => {
|
||||
entry: {
|
||||
prefixedName: nameForm,
|
||||
title: nameForm,
|
||||
properties: [{name: 'name'}, {name: 'email'}]
|
||||
properties: [{ name: 'name' }, { name: 'email' }]
|
||||
}
|
||||
}, {entry: {prefixedName: 'notme', title: 'notme'}}]
|
||||
}, { entry: { prefixedName: 'notme', title: 'notme' } }]
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -528,8 +567,10 @@ describe('FormService', () => {
|
||||
status: 200,
|
||||
statusText: 'HTTP/1.1 200 OK',
|
||||
contentType: 'text/xml;charset=UTF-8',
|
||||
responseText: {id: formId, name: 'test', lastUpdatedByFullName: 'user', lastUpdated: '12-12-2016'}
|
||||
responseText: { id: formId, name: 'test', lastUpdatedByFullName: 'user', lastUpdated: '12-12-2016' }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -158,7 +158,7 @@ export class FormService {
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
saveTaskForm(taskId: string, formValues: FormValues): Observable<any> {
|
||||
let body = JSON.stringify({values: formValues});
|
||||
let body = JSON.stringify({ values: formValues });
|
||||
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.saveTaskForm(taskId, body))
|
||||
.catch(err => this.handleError(err));
|
||||
@ -172,7 +172,7 @@ export class FormService {
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> {
|
||||
let data: any = {values: formValues};
|
||||
let data: any = { values: formValues };
|
||||
if (outcome) {
|
||||
data.outcome = outcome;
|
||||
}
|
||||
@ -294,67 +294,34 @@ export class FormService {
|
||||
return Observable.fromPromise(alfrescoApi.activiti.taskApi.getRestFieldValuesColumn(taskId, field, column));
|
||||
}
|
||||
|
||||
// TODO: uses private webApp api
|
||||
getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> {
|
||||
return Observable.create(observer => {
|
||||
|
||||
let xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
let json = JSON.parse(xhr.response);
|
||||
let data: GroupModel[] = (json.data || []).map(item => <GroupModel> item);
|
||||
observer.next(data);
|
||||
observer.complete();
|
||||
} else {
|
||||
this.logService.error(xhr.response);
|
||||
Observable.throw(new Error(xhr.response));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let host = this.apiService.getInstance().config.hostBpm;
|
||||
let url = `${host}/activiti-app/app/rest/workflow-groups?filter=${filter}`;
|
||||
if (groupId) {
|
||||
url += `&groupId=${groupId}`;
|
||||
}
|
||||
xhr.open('GET', url, true);
|
||||
xhr.setRequestHeader('Authorization', this.apiService.getInstance().getTicketBpm());
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
getWorkflowUsers(filter: string, groupId?: string): Observable<GroupUserModel[]> {
|
||||
return Observable.create(observer => {
|
||||
|
||||
let xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
let json = JSON.parse(xhr.response);
|
||||
let data: GroupUserModel[] = (json.data || []).map(item => <GroupUserModel> item);
|
||||
observer.next(data);
|
||||
observer.complete();
|
||||
} else {
|
||||
this.logService.error(xhr.response);
|
||||
Observable.throw(new Error(xhr.response));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let host = this.apiService.getInstance().config.hostBpm;
|
||||
let url = `${host}/activiti-app/app/rest/workflow-users?filter=${filter}`;
|
||||
let option: any = { filter: filter };
|
||||
if (groupId) {
|
||||
url += `&groupId=${groupId}`;
|
||||
option.groupId = groupId;
|
||||
}
|
||||
xhr.open('GET', url, true);
|
||||
xhr.setRequestHeader('Authorization', this.apiService.getInstance().getTicketBpm());
|
||||
xhr.send();
|
||||
});
|
||||
return Observable.fromPromise(this.getWorkflowUserApi(option))
|
||||
.map((response: any) => <GroupUserModel[]> response.data || [])
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
private getWorkflowUserApi(options: any) {
|
||||
let alfrescoApi = this.apiService.getInstance();
|
||||
return alfrescoApi.activiti.usersWorkflowApi.getUsers(options);
|
||||
}
|
||||
|
||||
getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> {
|
||||
let option: any = { filter: filter };
|
||||
if (groupId) {
|
||||
option.groupId = groupId;
|
||||
}
|
||||
return Observable.fromPromise(this.getWorkflowGroupsApi(option))
|
||||
.map((response: any) => <GroupModel[]> response.data || [])
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
private getWorkflowGroupsApi(options: any) {
|
||||
let alfrescoApi = this.apiService.getInstance();
|
||||
return alfrescoApi.activiti.groupsApi.getGroups(options);
|
||||
}
|
||||
|
||||
getFormId(res: any): string {
|
||||
|
@ -248,6 +248,25 @@ If both `appId` and `appName` are specified then `appName` will take precedence
|
||||
| `onError` | Emitted when an error occurs |
|
||||
| `ilterClick` | Emitted when the user selects a filter from the list |
|
||||
|
||||
### How to create an accordion menu with the processes filter
|
||||
|
||||
You can create an accordion menu using the AccordionComponent that wrap the activiti task filter.
|
||||
The AccordionComponent is exposed by the alfresco-core.
|
||||
|
||||
```html
|
||||
<adf-accordion>
|
||||
<adf-accordion-group [heading]="'Processes'" [isSelected]="true" [headingIcon]="'assessment'">
|
||||
<activiti-process-instance-filters
|
||||
[appId]="appId"
|
||||
(filterClick)="onProcessFilterClick($event)"
|
||||
(onSuccess)="onSuccessProcessFilterList($event)">
|
||||
</activiti-process-instance-filters>
|
||||
</adf-accordion-group>
|
||||
</adf-accordion>
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Start Process Button component
|
||||
|
||||
Displays a button which in turn displays a dialog when clicked, allowing the user
|
||||
|
@ -49,11 +49,11 @@
|
||||
"moment": "2.15.1",
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"ng2-activiti-tasklist": "1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-datatable": "1.3.0",
|
||||
"ng2-activiti-processlist": "1.3.0"
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"ng2-activiti-tasklist": "1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-alfresco-datatable": "1.4.0",
|
||||
"ng2-activiti-processlist": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.33",
|
||||
|
After Width: | Height: | Size: 56 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-processlist",
|
||||
"description": "Show active processes from the Activiti Process Services suite",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
|
||||
@ -54,15 +54,15 @@
|
||||
"@angular/platform-browser": "2.2.2",
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"core-js": "2.4.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"moment": "2.15.1",
|
||||
"ng2-activiti-form": "1.3.0",
|
||||
"ng2-activiti-tasklist": "1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-datatable": "1.3.0",
|
||||
"ng2-activiti-form": "1.4.0",
|
||||
"ng2-activiti-tasklist": "1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-alfresco-datatable": "1.4.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"reflect-metadata": "0.1.10",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
|
@ -18,17 +18,17 @@
|
||||
import {
|
||||
AppDefinitionRepresentationModel,
|
||||
Comment,
|
||||
FilterRepresentationModel,
|
||||
TaskDetailsModel,
|
||||
User
|
||||
} from 'ng2-activiti-tasklist';
|
||||
import { ProcessDefinitionRepresentation } from '../models/index';
|
||||
import { ProcessDefinitionRepresentation, FilterProcessRepresentationModel } from '../models/index';
|
||||
|
||||
export var fakeFilters = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [new FilterRepresentationModel({
|
||||
data: [new FilterProcessRepresentationModel({
|
||||
'name': 'Running',
|
||||
'appId': '22',
|
||||
'id': '333',
|
||||
'recent': true,
|
||||
'icon': 'glyphicon-random',
|
||||
'filter': {'sort': 'created-desc', 'name': '', 'state': 'running'}
|
||||
|
@ -14,6 +14,10 @@
|
||||
color: rgb(68,138,255);
|
||||
}
|
||||
|
||||
.activiti-filters__entry:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.activiti-filters__entry.active .activiti-filters__entry-icon {
|
||||
color: rgb(68,138,255);
|
||||
}
|
@ -20,7 +20,7 @@ import { Observable } from 'rxjs/Rx';
|
||||
import { LogServiceMock } from 'ng2-alfresco-core';
|
||||
import { ActivitiProcessFilters } from './activiti-filters.component';
|
||||
import { ActivitiProcessService } from '../services/activiti-process.service';
|
||||
import { FilterRepresentationModel } from 'ng2-activiti-tasklist';
|
||||
import { FilterProcessRepresentationModel } from '../models/filter-process.model';
|
||||
|
||||
describe('ActivitiFilters', () => {
|
||||
|
||||
@ -29,8 +29,8 @@ describe('ActivitiFilters', () => {
|
||||
let logService: LogServiceMock;
|
||||
|
||||
let fakeGlobalFilter = [];
|
||||
fakeGlobalFilter.push(new FilterRepresentationModel({name: 'FakeInvolvedTasks', filter: { state: 'open', assignment: 'fake-involved'}}));
|
||||
fakeGlobalFilter.push(new FilterRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}}));
|
||||
fakeGlobalFilter.push(new FilterProcessRepresentationModel({name: 'FakeInvolvedTasks', filter: { state: 'open', assignment: 'fake-involved'}}));
|
||||
fakeGlobalFilter.push(new FilterProcessRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}}));
|
||||
|
||||
let fakeGlobalFilterPromise = new Promise(function (resolve, reject) {
|
||||
resolve(fakeGlobalFilter);
|
||||
@ -121,9 +121,9 @@ describe('ActivitiFilters', () => {
|
||||
});
|
||||
|
||||
it('should emit an event when a filter is selected', (done) => {
|
||||
let currentFilter = new FilterRepresentationModel({filter: { state: 'open', assignment: 'fake-involved'}});
|
||||
let currentFilter = new FilterProcessRepresentationModel({filter: { state: 'open', assignment: 'fake-involved'}});
|
||||
|
||||
filterList.filterClick.subscribe((filter: FilterRepresentationModel) => {
|
||||
filterList.filterClick.subscribe((filter: FilterProcessRepresentationModel) => {
|
||||
expect(filter).toBeDefined();
|
||||
expect(filter).toEqual(currentFilter);
|
||||
expect(filterList.currentFilter).toEqual(currentFilter);
|
||||
@ -164,7 +164,7 @@ describe('ActivitiFilters', () => {
|
||||
});
|
||||
|
||||
it('should return the current filter after one is selected', () => {
|
||||
let filter = new FilterRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}});
|
||||
let filter = new FilterProcessRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}});
|
||||
expect(filterList.currentFilter).toBeUndefined();
|
||||
filterList.selectFilter(filter);
|
||||
expect(filterList.getCurrentFilter()).toBe(filter);
|
||||
|
@ -160,7 +160,7 @@ export class ActivitiProcessFilters implements OnInit, OnChanges {
|
||||
|
||||
/**
|
||||
* Return the current task
|
||||
* @returns {FilterRepresentationModel}
|
||||
* @returns {FilterProcessRepresentationModel}
|
||||
*/
|
||||
getCurrentFilter(): FilterProcessRepresentationModel {
|
||||
return this.currentFilter;
|
||||
|
@ -23,7 +23,7 @@
|
||||
* @returns {FilterProcessRepresentationModel} .
|
||||
*/
|
||||
export class FilterProcessRepresentationModel {
|
||||
id: number;
|
||||
id: string;
|
||||
appId: string;
|
||||
name: string;
|
||||
recent: boolean;
|
||||
@ -32,6 +32,7 @@ export class FilterProcessRepresentationModel {
|
||||
index: number;
|
||||
|
||||
constructor(obj?: any) {
|
||||
this.id = obj && obj.id || null;
|
||||
this.appId = obj && obj.appId || null;
|
||||
this.name = obj && obj.name || null;
|
||||
this.recent = obj && obj.recent || false;
|
||||
|
@ -18,7 +18,6 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { async } from '@angular/core/testing';
|
||||
import { CoreModule, AlfrescoApiService } from 'ng2-alfresco-core';
|
||||
import { FilterRepresentationModel } from 'ng2-activiti-tasklist';
|
||||
import { AlfrescoApi } from 'alfresco-js-api';
|
||||
import {
|
||||
fakeFilters,
|
||||
@ -581,7 +580,7 @@ describe('ActivitiProcessService', () => {
|
||||
|
||||
createFilter = spyOn(alfrescoApi.activiti.userFiltersApi, 'createUserProcessInstanceFilter')
|
||||
.and
|
||||
.callFake((filter: FilterRepresentationModel) => Promise.resolve(filter));
|
||||
.callFake((filter: FilterProcessRepresentationModel) => Promise.resolve(filter));
|
||||
});
|
||||
|
||||
describe('get filters', () => {
|
||||
@ -596,6 +595,32 @@ describe('ActivitiProcessService', () => {
|
||||
expect(getFilters).toHaveBeenCalledWith({appId: 226});
|
||||
});
|
||||
|
||||
it('should return the task filter by id', (done) => {
|
||||
service.getProcessFilterById('333').subscribe(
|
||||
(res: FilterProcessRepresentationModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual('333');
|
||||
expect(res.name).toEqual('Running');
|
||||
expect(res.filter.sort).toEqual('created-desc');
|
||||
expect(res.filter.state).toEqual('running');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the task filter by name', (done) => {
|
||||
service.getProcessFilterByName('Running').subscribe(
|
||||
(res: FilterProcessRepresentationModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual('333');
|
||||
expect(res.name).toEqual('Running');
|
||||
expect(res.filter.sort).toEqual('created-desc');
|
||||
expect(res.filter.state).toEqual('running');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the non-empty filter list that is returned by the API', async(() => {
|
||||
service.getProcessFilters(null).subscribe(
|
||||
(res) => {
|
||||
@ -640,7 +665,7 @@ describe('ActivitiProcessService', () => {
|
||||
});
|
||||
|
||||
it('should return the created filter', async(() => {
|
||||
service.addFilter(filter).subscribe((createdFilter: FilterRepresentationModel) => {
|
||||
service.addFilter(filter).subscribe((createdFilter: FilterProcessRepresentationModel) => {
|
||||
expect(createdFilter).toBe(filter);
|
||||
});
|
||||
}));
|
||||
|
@ -70,10 +70,34 @@ export class ActivitiProcessService {
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the process filter by id
|
||||
* @param processId - string - The id of the filter
|
||||
* @returns {Observable<FilterProcessRepresentationModel>}
|
||||
*/
|
||||
getProcessFilterById(processId: string, appId?: string): Observable<FilterProcessRepresentationModel> {
|
||||
return Observable.fromPromise(this.callApiGetUserProcessInstanceFilters(appId))
|
||||
.map((response: any) => {
|
||||
return response.data.find(filter => filter.id === processId);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the process filter by name
|
||||
* @param processName - string - The name of the filter
|
||||
* @returns {Observable<FilterProcessRepresentationModel>}
|
||||
*/
|
||||
getProcessFilterByName(processName: string, appId?: string): Observable<FilterProcessRepresentationModel> {
|
||||
return Observable.fromPromise(this.callApiGetUserProcessInstanceFilters(appId))
|
||||
.map((response: any) => {
|
||||
return response.data.find(filter => filter.name === processName);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return the default filters
|
||||
* @param appId
|
||||
* @returns {FilterRepresentationModel[]}
|
||||
* @returns {FilterProcessRepresentationModel[]}
|
||||
*/
|
||||
public createDefaultFilters(appId: number): Observable<FilterProcessRepresentationModel[]> {
|
||||
let runnintFilter = this.getRunningFilterInstance(appId);
|
||||
@ -294,6 +318,14 @@ export class ActivitiProcessService {
|
||||
return this.apiService.getInstance().activiti.userFiltersApi.createUserProcessInstanceFilter(filter);
|
||||
}
|
||||
|
||||
callApiProcessFilters(appId?: string) {
|
||||
if (appId) {
|
||||
return this.apiService.getInstance().activiti.userFiltersApi.getUserProcessInstanceFilters({ appId: appId });
|
||||
} else {
|
||||
return this.apiService.getInstance().activiti.userFiltersApi.getUserProcessInstanceFilters();
|
||||
}
|
||||
}
|
||||
|
||||
private extractData(res: any) {
|
||||
return res.data || {};
|
||||
}
|
||||
|
@ -211,11 +211,12 @@ Here's the list of available properties you can define for a Data Column definit
|
||||
### Properties
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| --- | --- | --- | --- |
|
||||
|`appId`| { string } The id of the app. |
|
||||
|`processDefinitionKey`| { string } The processDefinitionKey of the process. |
|
||||
|`assignment`| { string } The assignment of the process. <ul>Possible values are: <li>assignee : where the current user is the assignee</li> <li>candidate: where the current user is a task candidate </li><li>group_x: where the task is assigned to a group where the current user is a member of.</li> <li>no value: where the current user is involved</li> </ul> |
|
||||
|`state`| { string } Define state of the processes. Possible values are: completed, active |
|
||||
|`hasIcon` | boolean | true | Show/Hide the icon on the left . |
|
||||
|`landingTaskId`| { string } Define which task id should be selected after the reloading. If the task id doesn't exist or nothing is passed it will select the first task |
|
||||
|`sort`| { string } Define the sort of the processes. Possible values are : created-desc, created-asc, due-desc, due-asc |
|
||||
| `data` | { DataTableAdapter } (optional) JSON object that represent the number and the type of the columns that you want show |
|
||||
@ -224,10 +225,10 @@ Example:
|
||||
|
||||
```json
|
||||
[
|
||||
{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}
|
||||
{"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}
|
||||
]
|
||||
```
|
||||
|
||||
@ -310,6 +311,31 @@ The component shows all the available apps.
|
||||
| Name | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `layoutType` | {string} | required | Define the layout of the apps. There are two possible values: GRID or LIST. |
|
||||
| `filtersAppId` | {Object} | | Provide a way to filter the apps to show. |
|
||||
|
||||
|
||||
### How filter the activiti apps
|
||||
|
||||
If you want show some specific apps you can specify them through the filtersAppId parameters
|
||||
|
||||
```html
|
||||
<activiti-apps [filtersAppId]="'[{defaultAppId: 'tasks'}, {deploymentId: '15037'}, {name : 'my app name'}]'"></activiti-apps>
|
||||
```
|
||||
|
||||
In this specific case only the Tasks app, the app with deploymentId 15037 and the app with "my app name" will be showed
|
||||

|
||||
|
||||
You can use inside the filter one of the following property
|
||||
```json
|
||||
{
|
||||
"defaultAppId": "string",
|
||||
"deploymentId": "string",
|
||||
"name": "string",
|
||||
"id": "number",
|
||||
"modelId": "number",
|
||||
"tenantId": "number"
|
||||
}
|
||||
```
|
||||
|
||||
## Basic usage example Activiti Filter
|
||||
|
||||
@ -329,6 +355,27 @@ The component shows all the available filters.
|
||||
|
||||
No options
|
||||
|
||||
### How to create an accordion menu with the task filter
|
||||
|
||||
You can create an accordion menu using the AccordionComponent that wrap the activiti task filter.
|
||||
The AccordionComponent is exposed by the alfresco-core.
|
||||
|
||||
```html
|
||||
<adf-accordion>
|
||||
<adf-accordion-group [heading]="'Tasks'" [isSelected]="true" [headingIcon]="'assignment'">
|
||||
<activiti-filters
|
||||
[appId]="appId"
|
||||
[hasIcon]="false"
|
||||
(filterClick)="onTaskFilterClick($event)"
|
||||
(onSuccess)="onSuccessTaskFilterList($event)"
|
||||
#activitifilter>
|
||||
</activiti-filters>
|
||||
</adf-accordion-group>
|
||||
</adf-accordion>
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Basic usage example Activiti Checklist
|
||||
|
||||
The component shows the checklist task functionality.
|
||||
|
@ -43,10 +43,10 @@
|
||||
"moment": "2.15.1",
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-datatable": "1.3.0",
|
||||
"ng2-activiti-tasklist": "1.3.0"
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-alfresco-datatable": "1.4.0",
|
||||
"ng2-activiti-tasklist": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.33",
|
||||
|
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 84 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-tasklist",
|
||||
"description": "Activiti Angular2 Task List Component",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
|
||||
@ -59,14 +59,14 @@
|
||||
"@angular/platform-browser": "2.2.2",
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"core-js": "2.4.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"md-date-time-picker": "2.2.0",
|
||||
"moment": "2.15.1",
|
||||
"ng2-activiti-form": "1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-datatable": "1.3.0",
|
||||
"ng2-activiti-form": "1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-alfresco-datatable": "1.4.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"reflect-metadata": "0.1.10",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
|
@ -17,22 +17,6 @@
|
||||
|
||||
import { AppDefinitionRepresentationModel } from '../models/filter.model';
|
||||
|
||||
export var deployedApps = [new AppDefinitionRepresentationModel({
|
||||
id: '1',
|
||||
name: 'App1',
|
||||
icon: 'icon1',
|
||||
deploymentId: '1'
|
||||
}), new AppDefinitionRepresentationModel({
|
||||
id: '2',
|
||||
name: 'App2',
|
||||
icon: 'icon2',
|
||||
deploymentId: '2'
|
||||
}), new AppDefinitionRepresentationModel({
|
||||
id: '3',
|
||||
name: 'App3',
|
||||
icon: 'icon3',
|
||||
deploymentId: '3'
|
||||
})];
|
||||
export var nonDeployedApps = [new AppDefinitionRepresentationModel({
|
||||
id: '1',
|
||||
name: '1',
|
||||
@ -46,6 +30,50 @@ export var nonDeployedApps = [new AppDefinitionRepresentationModel({
|
||||
name: '3',
|
||||
icon: 'icon3'
|
||||
})];
|
||||
export var deployedApps = [new AppDefinitionRepresentationModel({
|
||||
id: 1,
|
||||
name: 'App1',
|
||||
icon: 'icon1',
|
||||
deploymentId: '1',
|
||||
defaultAppId: 'fake-app-1',
|
||||
modelId: null,
|
||||
tenantId: null
|
||||
}), new AppDefinitionRepresentationModel({
|
||||
id: 2,
|
||||
name: 'App2',
|
||||
icon: 'icon2',
|
||||
deploymentId: '2',
|
||||
modelId: null,
|
||||
tenantId: null
|
||||
}), new AppDefinitionRepresentationModel({
|
||||
id: 3,
|
||||
name: 'App3',
|
||||
icon: 'icon3',
|
||||
deploymentId: '3',
|
||||
modelId: null,
|
||||
tenantId: null
|
||||
}), new AppDefinitionRepresentationModel({
|
||||
id: 4,
|
||||
name: 'App4',
|
||||
icon: 'icon4',
|
||||
deploymentId: '4',
|
||||
modelId: 65,
|
||||
tenantId: null
|
||||
}), new AppDefinitionRepresentationModel({
|
||||
id: 5,
|
||||
name: 'App5',
|
||||
icon: 'icon5',
|
||||
deploymentId: '5',
|
||||
modelId: 66,
|
||||
tenantId: 9
|
||||
}), new AppDefinitionRepresentationModel({
|
||||
id: 6,
|
||||
name: 'App6',
|
||||
icon: 'icon6',
|
||||
deploymentId: '6',
|
||||
tenantId: 9,
|
||||
modelId: 66
|
||||
})];
|
||||
export var defaultApp = [new AppDefinitionRepresentationModel({
|
||||
defaultAppId: 'tasks'
|
||||
})];
|
||||
|
@ -0,0 +1,226 @@
|
||||
/*!
|
||||
* @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 {
|
||||
FilterRepresentationModel,
|
||||
AppDefinitionRepresentationModel
|
||||
} from '../models/filter.model';
|
||||
|
||||
export var fakeFilters = {
|
||||
size: 2, total: 2, start: 0,
|
||||
data: [
|
||||
new AppDefinitionRepresentationModel(
|
||||
{
|
||||
id: '1', name: 'FakeInvolvedTasks', recent: false, icon: 'glyphicon-align-left',
|
||||
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved' }
|
||||
}
|
||||
),
|
||||
{
|
||||
id: '2', name: 'FakeMyTasks', recent: false, icon: 'glyphicon-align-left',
|
||||
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-assignee' }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var fakeAppFilter = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, name: 'FakeInvolvedTasks', recent: false, icon: 'glyphicon-align-left',
|
||||
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved' }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var fakeApps = {
|
||||
size: 2, total: 2, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, defaultAppId: null, name: 'Sales-Fakes-App', description: 'desc-fake1', modelId: 22,
|
||||
theme: 'theme-1-fake', icon: 'glyphicon-asterisk', 'deploymentId': '111', 'tenantId': null
|
||||
},
|
||||
{
|
||||
id: 2, defaultAppId: null, name: 'health-care-Fake', description: 'desc-fake2', modelId: 33,
|
||||
theme: 'theme-2-fake', icon: 'glyphicon-asterisk', 'deploymentId': '444', 'tenantId': null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var fakeFilter = {
|
||||
sort: 'created-desc', text: '', state: 'open', assignment: 'fake-assignee'
|
||||
};
|
||||
|
||||
export var fakeFilterWithProcessDefinitionKey = {
|
||||
sort: 'created-desc', text: '', state: 'open', assignment: 'fake-assignee', processDefinitionKey: '1'
|
||||
};
|
||||
|
||||
export var fakeUser = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' };
|
||||
|
||||
export var fakeTaskList = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: '1', name: 'FakeNameTask', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var fakeTaskListDifferentProcessDefinitionKey = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: '1', name: 'FakeNameTask', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
processDefinitionKey: '1',
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
},
|
||||
{
|
||||
id: '2', name: 'FakeNameTask2', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
processDefinitionKey: '2',
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var secondFakeTaskList = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: '200', name: 'FakeNameTask', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var fakeErrorTaskList = {
|
||||
error: 'wrong request'
|
||||
};
|
||||
|
||||
export var fakeTaskDetails = { id: '999', name: 'fake-task-name', formKey: '99', assignee: fakeUser };
|
||||
|
||||
export var fakeTasksComment = {
|
||||
size: 2, total: 2, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, message: 'fake-message-1', created: '', createdBy: fakeUser
|
||||
},
|
||||
{
|
||||
id: 2, message: 'fake-message-2', created: '', createdBy: fakeUser
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var fakeTasksChecklist = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, name: 'FakeCheckTask1', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
},
|
||||
{
|
||||
id: 2, name: 'FakeCheckTask2', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export var fakeRepresentationFilter1: FilterRepresentationModel = new FilterRepresentationModel({
|
||||
appId: 1,
|
||||
name: 'CONTAIN FILTER',
|
||||
recent: true,
|
||||
icon: 'glyphicon-align-left',
|
||||
filter: {
|
||||
processDefinitionId: null,
|
||||
processDefinitionKey: null,
|
||||
name: null,
|
||||
state: 'open',
|
||||
sort: 'created-desc',
|
||||
assignment: 'involved',
|
||||
dueAfter: null,
|
||||
dueBefore: null
|
||||
}
|
||||
});
|
||||
|
||||
export var fakeRepresentationFilter2: FilterRepresentationModel = new FilterRepresentationModel({
|
||||
appId: 2,
|
||||
name: 'NO TASK FILTER',
|
||||
recent: false,
|
||||
icon: 'glyphicon-inbox',
|
||||
filter: {
|
||||
processDefinitionId: null,
|
||||
processDefinitionKey: null,
|
||||
name: null,
|
||||
state: 'open',
|
||||
sort: 'created-desc',
|
||||
assignment: 'assignee',
|
||||
dueAfter: null,
|
||||
dueBefore: null
|
||||
}
|
||||
});
|
||||
|
||||
export var fakeAppPromise = new Promise(function (resolve, reject) {
|
||||
resolve(fakeAppFilter);
|
||||
});
|
||||
|
||||
export var fakeFormList = {
|
||||
size: 2,
|
||||
total: 2,
|
||||
start: 0,
|
||||
data: [{
|
||||
id: 1,
|
||||
name: 'form with all widgets',
|
||||
description: '',
|
||||
createdBy: 2,
|
||||
createdByFullName: 'Admin Admin',
|
||||
lastUpdatedBy: 2,
|
||||
lastUpdatedByFullName: 'Admin Admin',
|
||||
lastUpdated: 1491400951205,
|
||||
latestVersion: true,
|
||||
version: 4,
|
||||
comment: null,
|
||||
stencilSet: null,
|
||||
referenceId: null,
|
||||
modelType: 2,
|
||||
favorite: null,
|
||||
permission: 'write',
|
||||
tenantId: null
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'uppy',
|
||||
description: '',
|
||||
createdBy: 2,
|
||||
createdByFullName: 'Admin Admin',
|
||||
lastUpdatedBy: 2,
|
||||
lastUpdatedByFullName: 'Admin Admin',
|
||||
lastUpdated: 1490951054477,
|
||||
latestVersion: true,
|
||||
version: 2,
|
||||
comment: null,
|
||||
stencilSet: null,
|
||||
referenceId: null,
|
||||
modelType: 2,
|
||||
favorite: null,
|
||||
permission: 'write',
|
||||
tenantId: null
|
||||
}]
|
||||
};
|
@ -22,3 +22,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-container" *ngIf="isEmpty()">
|
||||
{{ 'APPS.NONE' | translate }}
|
||||
</div>
|
||||
|
@ -59,7 +59,7 @@ describe('ActivitiApps', () => {
|
||||
debugElement = fixture.debugElement;
|
||||
|
||||
service = fixture.debugElement.injector.get(ActivitiTaskListService);
|
||||
getAppsSpy = spyOn(service, 'getDeployedApplications').and.returnValue(Observable.of());
|
||||
getAppsSpy = spyOn(service, 'getDeployedApplications').and.returnValue(Observable.of(deployedApps));
|
||||
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', [
|
||||
'upgradeAllRegistered',
|
||||
@ -79,6 +79,59 @@ describe('ActivitiApps', () => {
|
||||
expect(getAppsSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show the apps filterd by defaultAppId', () => {
|
||||
component.filtersAppId = [{defaultAppId: 'fake-app-1'}];
|
||||
fixture.detectChanges();
|
||||
expect(component.isEmpty()).toBe(false);
|
||||
expect(component.appList).toBeDefined();
|
||||
expect(component.appList.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should show the apps filterd by deploymentId', () => {
|
||||
component.filtersAppId = [{deploymentId: '4'}];
|
||||
fixture.detectChanges();
|
||||
expect(component.isEmpty()).toBe(false);
|
||||
expect(component.appList).toBeDefined();
|
||||
expect(component.appList.length).toEqual(1);
|
||||
expect(component.appList[0].deploymentId).toEqual('4');
|
||||
});
|
||||
|
||||
it('should show the apps filterd by name', () => {
|
||||
component.filtersAppId = [{name: 'App5'}];
|
||||
fixture.detectChanges();
|
||||
expect(component.isEmpty()).toBe(false);
|
||||
expect(component.appList).toBeDefined();
|
||||
expect(component.appList.length).toEqual(1);
|
||||
expect(component.appList[0].name).toEqual('App5');
|
||||
});
|
||||
|
||||
it('should show the apps filterd by id', () => {
|
||||
component.filtersAppId = [{id: 6}];
|
||||
fixture.detectChanges();
|
||||
expect(component.isEmpty()).toBe(false);
|
||||
expect(component.appList).toBeDefined();
|
||||
expect(component.appList.length).toEqual(1);
|
||||
expect(component.appList[0].id).toEqual(6);
|
||||
});
|
||||
|
||||
it('should show the apps filterd by modelId', () => {
|
||||
component.filtersAppId = [{modelId: 66}];
|
||||
fixture.detectChanges();
|
||||
expect(component.isEmpty()).toBe(false);
|
||||
expect(component.appList).toBeDefined();
|
||||
expect(component.appList.length).toEqual(2);
|
||||
expect(component.appList[0].modelId).toEqual(66);
|
||||
});
|
||||
|
||||
it('should show the apps filterd by tenandId', () => {
|
||||
component.filtersAppId = [{tenantId: 9}];
|
||||
fixture.detectChanges();
|
||||
expect(component.isEmpty()).toBe(false);
|
||||
expect(component.appList).toBeDefined();
|
||||
expect(component.appList.length).toEqual(2);
|
||||
expect(component.appList[0].tenantId).toEqual(9);
|
||||
});
|
||||
|
||||
it('should emit an error when an error occurs loading apps', () => {
|
||||
let emitSpy = spyOn(component.error, 'emit');
|
||||
getAppsSpy.and.returnValue(Observable.throw({}));
|
||||
@ -119,7 +172,7 @@ describe('ActivitiApps', () => {
|
||||
it('should display all deployed apps', () => {
|
||||
getAppsSpy.and.returnValue(Observable.of(deployedApps));
|
||||
fixture.detectChanges();
|
||||
expect(debugElement.queryAll(By.css('h1')).length).toBe(3);
|
||||
expect(debugElement.queryAll(By.css('h1')).length).toBe(6);
|
||||
});
|
||||
|
||||
it('should not display undeployed apps', () => {
|
||||
|
@ -45,6 +45,9 @@ export class ActivitiApps implements OnInit {
|
||||
@Input()
|
||||
layoutType: string = ActivitiApps.LAYOUT_GRID;
|
||||
|
||||
@Input()
|
||||
filtersAppId: any[];
|
||||
|
||||
@Output()
|
||||
appClick: EventEmitter<AppDefinitionRepresentationModel> = new EventEmitter<AppDefinitionRepresentationModel>();
|
||||
|
||||
@ -90,6 +93,7 @@ export class ActivitiApps implements OnInit {
|
||||
private load() {
|
||||
this.activitiTaskList.getDeployedApplications().subscribe(
|
||||
(res) => {
|
||||
res = this.filterApps(res);
|
||||
res.forEach((app: AppDefinitionRepresentationModel) => {
|
||||
if (app.defaultAppId === ActivitiApps.DEFAULT_TASKS_APP) {
|
||||
app.name = ActivitiApps.DEFAULT_TASKS_APP_NAME;
|
||||
@ -125,6 +129,27 @@ export class ActivitiApps implements OnInit {
|
||||
return (this.currentApp !== undefined && appId === this.currentApp.id);
|
||||
}
|
||||
|
||||
private filterApps(apps: AppDefinitionRepresentationModel []): AppDefinitionRepresentationModel[] {
|
||||
let filteredApps = [];
|
||||
if (this.filtersAppId) {
|
||||
apps.filter((app: AppDefinitionRepresentationModel) => {
|
||||
this.filtersAppId.forEach((filter) => {
|
||||
if (app.defaultAppId === filter.defaultAppId ||
|
||||
app.deploymentId === filter.deploymentId ||
|
||||
app.name === filter.name ||
|
||||
app.id === filter.id ||
|
||||
app.modelId === filter.modelId ||
|
||||
app.tenantId === filter.tenantId) {
|
||||
filteredApps.push(app);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return apps;
|
||||
}
|
||||
return filteredApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value of the layoutType property is an allowed value
|
||||
* @returns {boolean}
|
||||
|
@ -7,9 +7,11 @@
|
||||
<div class="menu-container" *ngIf="checklist?.length > 0">
|
||||
<ul class='mdl-list'>
|
||||
<li class="mdl-list__item" *ngFor="let check of checklist">
|
||||
<span class="mdl-list__item-primary-content" id="check-{{check.id}}">
|
||||
<i class="material-icons mdl-list__item-icon">done</i>
|
||||
{{check.name}}
|
||||
<span class="mdl-chip mdl-chip--deletable" id="check-{{check.id}}">
|
||||
<span class="mdl-chip__text">{{check.name}}</span>
|
||||
<button type="button" class="mdl-chip__action" (click)="delete(check.id)">
|
||||
<i id="remove-{{check.id}}" class="material-icons">cancel</i>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -140,6 +140,42 @@ describe('ActivitiChecklist', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should remove a checklist element', async(() => {
|
||||
checklistComponent.taskId = 'new-fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
fixture.detectChanges();
|
||||
let checklistElementRemove = <HTMLElement> element.querySelector('#remove-fake-check-id');
|
||||
expect(checklistElementRemove).toBeDefined();
|
||||
expect(checklistElementRemove).not.toBeNull();
|
||||
checklistElementRemove.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json'
|
||||
});
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#fake-check-id')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should send an event when the checklist is deleted', (done) => {
|
||||
checklistComponent.taskId = 'new-fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
fixture.detectChanges();
|
||||
let checklistElementRemove = <HTMLElement> element.querySelector('#remove-fake-check-id');
|
||||
expect(checklistElementRemove).toBeDefined();
|
||||
expect(checklistElementRemove).not.toBeNull();
|
||||
checklistElementRemove.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json'
|
||||
});
|
||||
checklistComponent.checklistTaskDeleted.subscribe(() => {
|
||||
expect(element.querySelector('#fake-check-id')).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show load task checklist on change', async(() => {
|
||||
checklistComponent.taskId = 'new-fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
|
@ -44,6 +44,9 @@ export class ActivitiChecklist implements OnInit, OnChanges {
|
||||
@Output()
|
||||
checklistTaskCreated: EventEmitter<TaskDetailsModel> = new EventEmitter<TaskDetailsModel>();
|
||||
|
||||
@Output()
|
||||
checklistTaskDeleted: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
@ViewChild('dialog')
|
||||
dialog: any;
|
||||
|
||||
@ -129,6 +132,17 @@ export class ActivitiChecklist implements OnInit, OnChanges {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
public delete(taskId: string) {
|
||||
this.activitiTaskList.deleteTask(taskId).subscribe(
|
||||
() => {
|
||||
this.checklist = this.checklist.filter(check => check.id !== taskId);
|
||||
this.checklistTaskDeleted.emit(taskId);
|
||||
},
|
||||
(err) => {
|
||||
this.logService.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
if (this.dialog) {
|
||||
this.dialog.nativeElement.close();
|
||||
|
@ -14,6 +14,10 @@
|
||||
color: rgb(68,138,255);
|
||||
}
|
||||
|
||||
.activiti-filters__entry:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.activiti-filters__entry.active .activiti-filters__entry-icon {
|
||||
color: rgb(68,138,255);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<li class="mdl-list__item activiti-filters__entry" (click)="selectFilter(filter)" *ngFor="let filter of filters"
|
||||
[class.active]="currentFilter === filter">
|
||||
<span class="mdl-list__item-primary-content">
|
||||
<i class="material-icons mdl-list__item-icon activiti-filters__entry-icon" [attr.data-automation-id]="filter.name + '_filter'" >assignment</i>
|
||||
<i *ngIf="hasIcon" class="material-icons mdl-list__item-icon activiti-filters__entry-icon" [attr.data-automation-id]="filter.name + '_filter'" >assignment</i>
|
||||
{{filter.name}}
|
||||
</span>
|
||||
</li>
|
||||
|
@ -47,6 +47,9 @@ export class ActivitiFilters implements OnInit, OnChanges {
|
||||
@Input()
|
||||
appName: string;
|
||||
|
||||
@Input()
|
||||
hasIcon: boolean = true;
|
||||
|
||||
private filterObserver: Observer<FilterRepresentationModel>;
|
||||
filter$: Observable<FilterRepresentationModel>;
|
||||
|
||||
|
@ -38,13 +38,15 @@
|
||||
[readOnly]="readOnlyForm"
|
||||
[taskId]="taskDetails.id"
|
||||
[assignee]="taskDetails?.assignee?.id"
|
||||
(checklistTaskCreated)="onChecklistTaskCreated($event)">
|
||||
(checklistTaskCreated)="onChecklistTaskCreated($event)"
|
||||
(checklistTaskDeleted)="onChecklistTaskDeleted($event)">
|
||||
</activiti-checklist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isAssignedToMe()">
|
||||
<activiti-form *ngIf="hasFormKey()" #activitiForm
|
||||
[showDebugButton]="debugMode"
|
||||
[taskId]="taskDetails.id"
|
||||
[showTitle]="showFormTitle"
|
||||
[showRefreshButton]="showFormRefreshButton"
|
||||
|
@ -42,6 +42,9 @@ export class ActivitiTaskDetails implements OnInit, OnChanges {
|
||||
@ViewChild('errorDialog')
|
||||
errorDialog: DebugElement;
|
||||
|
||||
@Input()
|
||||
debugMode: boolean = false;
|
||||
|
||||
@Input()
|
||||
taskId: string;
|
||||
|
||||
@ -96,6 +99,9 @@ export class ActivitiTaskDetails implements OnInit, OnChanges {
|
||||
@Output()
|
||||
taskCreated: EventEmitter<TaskDetailsModel> = new EventEmitter<TaskDetailsModel>();
|
||||
|
||||
@Output()
|
||||
taskDeleted: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
onError: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ -258,6 +264,10 @@ export class ActivitiTaskDetails implements OnInit, OnChanges {
|
||||
this.taskCreated.emit(task);
|
||||
}
|
||||
|
||||
onChecklistTaskDeleted(taskId: string) {
|
||||
this.taskDeleted.emit(taskId);
|
||||
}
|
||||
|
||||
onFormError(error: any) {
|
||||
this.errorDialog.nativeElement.showModal();
|
||||
this.onError.emit(error);
|
||||
|
@ -1,4 +1,7 @@
|
||||
{
|
||||
"APPS": {
|
||||
"NONE": "No apps found."
|
||||
},
|
||||
"TASK_LIST": {
|
||||
"MESSAGES": {
|
||||
"NONE": "No tasks list found."
|
||||
|
@ -1,4 +1,7 @@
|
||||
{
|
||||
"APPS": {
|
||||
"NONE": "Nessuna applicazione trovata."
|
||||
},
|
||||
"TASK_LIST": {
|
||||
"MESSAGES": {
|
||||
"NONE": "Nessuna lista tasks trovata."
|
||||
|
@ -54,7 +54,7 @@ export class AppDefinitionRepresentationModel {
|
||||
* @returns {FilterRepresentationModel} .
|
||||
*/
|
||||
export class FilterRepresentationModel {
|
||||
id: number;
|
||||
id: string;
|
||||
appId: string;
|
||||
name: string;
|
||||
recent: boolean;
|
||||
@ -64,6 +64,7 @@ export class FilterRepresentationModel {
|
||||
landingTaskId: string;
|
||||
|
||||
constructor(obj?: any) {
|
||||
this.id = obj && obj.id || null;
|
||||
this.appId = obj && obj.appId || null;
|
||||
this.name = obj && obj.name || null;
|
||||
this.recent = obj && obj.recent || false;
|
||||
|
@ -15,173 +15,65 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { async } from '@angular/core/testing';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
StorageService,
|
||||
LogService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { ActivitiTaskListService } from './activiti-tasklist.service';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import {
|
||||
FilterRepresentationModel,
|
||||
AppDefinitionRepresentationModel,
|
||||
TaskQueryRequestRepresentationModel
|
||||
} from '../models/filter.model';
|
||||
import { Comment } from '../models/comment.model';
|
||||
import {
|
||||
fakeFilters,
|
||||
fakeAppPromise,
|
||||
fakeAppFilter,
|
||||
fakeFilter,
|
||||
fakeTaskList,
|
||||
fakeErrorTaskList,
|
||||
fakeTasksComment,
|
||||
fakeTasksChecklist,
|
||||
fakeTaskDetails,
|
||||
fakeUser,
|
||||
fakeApps,
|
||||
fakeRepresentationFilter1,
|
||||
secondFakeTaskList,
|
||||
fakeRepresentationFilter2,
|
||||
fakeFormList,
|
||||
fakeTaskListDifferentProcessDefinitionKey,
|
||||
fakeFilterWithProcessDefinitionKey
|
||||
} from '../assets/tasklist-service.mock';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('ActivitiTaskListService', () => {
|
||||
|
||||
let fakeFilters = {
|
||||
size: 2, total: 2, start: 0,
|
||||
data: [
|
||||
new AppDefinitionRepresentationModel(
|
||||
{
|
||||
id: '1', name: 'FakeInvolvedTasks', recent: false, icon: 'glyphicon-align-left',
|
||||
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved' }
|
||||
}
|
||||
),
|
||||
{
|
||||
id: '2', name: 'FakeMyTasks', recent: false, icon: 'glyphicon-align-left',
|
||||
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-assignee' }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let fakeAppFilter = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, name: 'FakeInvolvedTasks', recent: false, icon: 'glyphicon-align-left',
|
||||
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved' }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let fakeApps = {
|
||||
size: 2, total: 2, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, defaultAppId: null, name: 'Sales-Fakes-App', description: 'desc-fake1', modelId: 22,
|
||||
theme: 'theme-1-fake', icon: 'glyphicon-asterisk', 'deploymentId': '111', 'tenantId': null
|
||||
},
|
||||
{
|
||||
id: 2, defaultAppId: null, name: 'health-care-Fake', description: 'desc-fake2', modelId: 33,
|
||||
theme: 'theme-2-fake', icon: 'glyphicon-asterisk', 'deploymentId': '444', 'tenantId': null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let fakeFilter = {
|
||||
sort: 'created-desc', text: '', state: 'open', assignment: 'fake-assignee'
|
||||
};
|
||||
|
||||
let fakeUser = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' };
|
||||
|
||||
let fakeTaskList = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: '1', name: 'FakeNameTask', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let secondFakeTaskList = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: '200', name: 'FakeNameTask', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let fakeErrorTaskList = {
|
||||
error: 'wrong request'
|
||||
};
|
||||
|
||||
let fakeTaskDetails = { id: '999', name: 'fake-task-name', formKey: '99', assignee: fakeUser };
|
||||
|
||||
let fakeTasksComment = {
|
||||
size: 2, total: 2, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, message: 'fake-message-1', created: '', createdBy: fakeUser
|
||||
},
|
||||
{
|
||||
id: 2, message: 'fake-message-2', created: '', createdBy: fakeUser
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let fakeTasksChecklist = {
|
||||
size: 1, total: 1, start: 0,
|
||||
data: [
|
||||
{
|
||||
id: 1, name: 'FakeCheckTask1', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
},
|
||||
{
|
||||
id: 2, name: 'FakeCheckTask2', description: null, category: null,
|
||||
assignee: fakeUser,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let fakeRepresentationFilter1: FilterRepresentationModel = new FilterRepresentationModel({
|
||||
appId: 1,
|
||||
name: 'CONTAIN FILTER',
|
||||
recent: true,
|
||||
icon: 'glyphicon-align-left',
|
||||
filter: {
|
||||
processDefinitionId: null,
|
||||
processDefinitionKey: null,
|
||||
name: null,
|
||||
state: 'open',
|
||||
sort: 'created-desc',
|
||||
assignment: 'involved',
|
||||
dueAfter: null,
|
||||
dueBefore: null
|
||||
}
|
||||
});
|
||||
|
||||
let fakeRepresentationFilter2: FilterRepresentationModel = new FilterRepresentationModel({
|
||||
appId: 2,
|
||||
name: 'NO TASK FILTER',
|
||||
recent: false,
|
||||
icon: 'glyphicon-inbox',
|
||||
filter: {
|
||||
processDefinitionId: null,
|
||||
processDefinitionKey: null,
|
||||
name: null,
|
||||
state: 'open',
|
||||
sort: 'created-desc',
|
||||
assignment: 'assignee',
|
||||
dueAfter: null,
|
||||
dueBefore: null
|
||||
}
|
||||
});
|
||||
|
||||
let fakeAppPromise = new Promise(function (resolve, reject) {
|
||||
resolve(fakeAppFilter);
|
||||
});
|
||||
describe('Activiti TaskList Service', () => {
|
||||
|
||||
let service: ActivitiTaskListService;
|
||||
let injector;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
ActivitiTaskListService
|
||||
]
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
AlfrescoAuthenticationService,
|
||||
ActivitiTaskListService,
|
||||
StorageService,
|
||||
LogService
|
||||
]);
|
||||
});
|
||||
service = TestBed.get(ActivitiTaskListService);
|
||||
|
||||
beforeEach(() => {
|
||||
service = injector.get(ActivitiTaskListService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
@ -189,6 +81,8 @@ describe('ActivitiTaskListService', () => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
describe('Content tests', () => {
|
||||
|
||||
it('should return the task list filters', (done) => {
|
||||
service.getTaskListFilters().subscribe(
|
||||
(res) => {
|
||||
@ -207,6 +101,46 @@ describe('ActivitiTaskListService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task filter by id', (done) => {
|
||||
service.getTaskFilterById('2').subscribe(
|
||||
(res: FilterRepresentationModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual('2');
|
||||
expect(res.name).toEqual('FakeMyTasks');
|
||||
expect(res.filter.sort).toEqual('created-desc');
|
||||
expect(res.filter.state).toEqual('open');
|
||||
expect(res.filter.assignment).toEqual('fake-assignee');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeFilters)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task filter by name', (done) => {
|
||||
service.getTaskFilterByName('FakeMyTasks').subscribe(
|
||||
(res: FilterRepresentationModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual('2');
|
||||
expect(res.name).toEqual('FakeMyTasks');
|
||||
expect(res.filter.sort).toEqual('created-desc');
|
||||
expect(res.filter.state).toEqual('open');
|
||||
expect(res.filter.assignment).toEqual('fake-assignee');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeFilters)
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the api withthe appId', (done) => {
|
||||
spyOn(service, 'callApiTaskFilters').and.returnValue((fakeAppPromise));
|
||||
|
||||
@ -257,6 +191,27 @@ describe('ActivitiTaskListService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task list filtered by processDefinitionKey', (done) => {
|
||||
service.getTasks(<TaskQueryRequestRepresentationModel>fakeFilterWithProcessDefinitionKey).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(1);
|
||||
expect(res[0].name).toEqual('FakeNameTask');
|
||||
expect(res[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res[0].assignee.lastName).toEqual('lastName');
|
||||
expect(res[0].processDefinitionKey).toEqual('1');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskListDifferentProcessDefinitionKey)
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an exception when the response is wrong', () => {
|
||||
service.getTasks(<TaskQueryRequestRepresentationModel>fakeFilter).subscribe(
|
||||
(res) => {
|
||||
@ -369,6 +324,19 @@ describe('ActivitiTaskListService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove a checklist task ', (done) => {
|
||||
service.deleteTask('999').subscribe(
|
||||
() => {
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a comment task ', (done) => {
|
||||
service.addTaskComment('999', 'fake-comment-message').subscribe(
|
||||
(res: Comment) => {
|
||||
@ -646,4 +614,25 @@ describe('ActivitiTaskListService', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should get possibile form list', (done) => {
|
||||
service.getFormList().subscribe(
|
||||
(res: any) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toBe(2);
|
||||
expect(res[0].id).toBe(1);
|
||||
expect(res[0].name).toBe('form with all widgets');
|
||||
expect(res[1].id).toBe(2);
|
||||
expect(res[1].name).toBe('uppy');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeFormList)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ export class ActivitiTaskListService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive all the Deployed app
|
||||
* Retrieve all the Deployed app
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
getDeployedApplications(name?: string): Observable<any> {
|
||||
@ -50,7 +50,7 @@ export class ActivitiTaskListService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive all the Tasks filters
|
||||
* Retrieve all the Tasks filters
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
getTaskListFilters(appId?: string): Observable<any> {
|
||||
@ -65,6 +65,30 @@ export class ActivitiTaskListService {
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Tasks filter by id
|
||||
* @param taskId - string - The id of the filter
|
||||
* @returns {Observable<FilterRepresentationModel>}
|
||||
*/
|
||||
getTaskFilterById(taskId: string, appId?: string): Observable<FilterRepresentationModel> {
|
||||
return Observable.fromPromise(this.callApiTaskFilters(appId))
|
||||
.map((response: any) => {
|
||||
return response.data.find(filter => filter.id === taskId);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Tasks filter by name
|
||||
* @param taskName - string - The name of the filter
|
||||
* @returns {Observable<FilterRepresentationModel>}
|
||||
*/
|
||||
getTaskFilterByName(taskName: string, appId?: string): Observable<FilterRepresentationModel> {
|
||||
return Observable.fromPromise(this.callApiTaskFilters(appId))
|
||||
.map((response: any) => {
|
||||
return response.data.find(filter => filter.name === taskName);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the filters in the list where the task id belong
|
||||
* @param taskId - string
|
||||
@ -108,7 +132,7 @@ export class ActivitiTaskListService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive all the tasks filtered by filterModel
|
||||
* Retrieve all the tasks filtered by filterModel
|
||||
* @param filter - TaskFilterRepresentationModel
|
||||
* @returns {any}
|
||||
*/
|
||||
@ -124,7 +148,7 @@ export class ActivitiTaskListService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive all the task details
|
||||
* Retrieve all the task details
|
||||
* @param id - taskId
|
||||
* @returns {<TaskDetailsModel>}
|
||||
*/
|
||||
@ -137,7 +161,7 @@ export class ActivitiTaskListService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive all the task's comments
|
||||
* Retrieve all the task's comments
|
||||
* @param id - taskId
|
||||
* @returns {<Comment[]>}
|
||||
*/
|
||||
@ -155,7 +179,7 @@ export class ActivitiTaskListService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive all the task's checklist
|
||||
* Retrieve all the task's checklist
|
||||
* @param id - taskId
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
@ -172,7 +196,7 @@ export class ActivitiTaskListService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive all the form shared with this user
|
||||
* Retrieve all the form shared with this user
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
getFormList(): Observable<Form []> {
|
||||
@ -256,6 +280,15 @@ export class ActivitiTaskListService {
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a task
|
||||
* @param taskId - string
|
||||
*/
|
||||
deleteTask(taskId: string): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.callApiDeleteTask(taskId))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter
|
||||
* @param filter - FilterRepresentationModel
|
||||
@ -357,6 +390,10 @@ export class ActivitiTaskListService {
|
||||
return this.apiService.getInstance().activiti.taskApi.addSubtask(task.parentTaskId, task);
|
||||
}
|
||||
|
||||
private callApiDeleteTask(taskId: string) {
|
||||
return this.apiService.getInstance().activiti.taskApi.deleteTask(taskId);
|
||||
}
|
||||
|
||||
private callApiAddFilter(filter: FilterRepresentationModel) {
|
||||
return this.apiService.getInstance().activiti.userFiltersApi.createUserTaskFilter(filter);
|
||||
}
|
||||
|
@ -45,11 +45,13 @@ import { DataColumnComponent } from './src/components/data-column/data-column.co
|
||||
import { DataColumnListComponent } from './src/components/data-column/data-column-list.component';
|
||||
import { MATERIAL_DESIGN_DIRECTIVES } from './src/components/material/index';
|
||||
import { CONTEXT_MENU_PROVIDERS, CONTEXT_MENU_DIRECTIVES } from './src/components/context-menu/index';
|
||||
import { COLLAPSABLE_DIRECTIVES } from './src/components/collapsable/index';
|
||||
|
||||
export * from './src/services/index';
|
||||
export * from './src/components/index';
|
||||
export * from './src/components/data-column/data-column.component';
|
||||
export * from './src/components/data-column/data-column-list.component';
|
||||
export * from './src/components/collapsable/index';
|
||||
export * from './src/directives/upload.directive';
|
||||
export * from './src/utils/index';
|
||||
export * from './src/events/base.event';
|
||||
@ -94,6 +96,7 @@ export function createTranslateLoader(http: Http, logService: LogService) {
|
||||
declarations: [
|
||||
...MATERIAL_DESIGN_DIRECTIVES,
|
||||
...CONTEXT_MENU_DIRECTIVES,
|
||||
...COLLAPSABLE_DIRECTIVES,
|
||||
UploadDirective,
|
||||
DataColumnComponent,
|
||||
DataColumnListComponent
|
||||
@ -110,6 +113,7 @@ export function createTranslateLoader(http: Http, logService: LogService) {
|
||||
TranslateModule,
|
||||
...MATERIAL_DESIGN_DIRECTIVES,
|
||||
...CONTEXT_MENU_DIRECTIVES,
|
||||
...COLLAPSABLE_DIRECTIVES,
|
||||
UploadDirective,
|
||||
DataColumnComponent,
|
||||
DataColumnListComponent
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-alfresco-core",
|
||||
"description": "Alfresco Angular 2 Components core",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
|
||||
@ -65,7 +65,7 @@
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"@angular/upgrade": "2.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"core-js": "2.4.1",
|
||||
"dialog-polyfill": "0.4.7",
|
||||
"element.scrollintoviewifneeded-polyfill": "1.0.1",
|
||||
|
@ -0,0 +1,35 @@
|
||||
.adf-panel-heading {
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: normal;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.adf-panel-heading-selected {
|
||||
color: #448aff;
|
||||
}
|
||||
|
||||
.adf-panel-heading-icon {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.adf-panel-heading-text {
|
||||
float: left;
|
||||
padding-left: 20px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.adf-panel-heading-toggle {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.adf-panel-heading-toggle:hover {
|
||||
opacity: 0.4;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<div class="adf-panel adf-panel-default" [ngClass]="{'adf-panel-open': isOpen}">
|
||||
<div class="adf-panel-heading" [ngClass]="{'adf-panel-heading-selected': isSelected}">
|
||||
<div *ngIf="hasHeadingIcon()" class="adf-panel-heading-icon">
|
||||
<i class="material-icons">{{headingIcon}}</i>
|
||||
</div>
|
||||
<div class="adf-panel-heading-text">{{heading}}</div>
|
||||
<div id="accordion-button" class="adf-panel-heading-toggle" (click)="toggleOpen($event)">
|
||||
<i class="material-icons">{{getAccordionIcon()}}</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adf-panel-collapse" [hidden]="!isOpen">
|
||||
<div class="adf-panel-body">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,76 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { AccordionComponent } from './accordion.component';
|
||||
import { AccordionGroupComponent } from './accordion-group.component';
|
||||
|
||||
describe('AccordionGroupComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<AccordionGroupComponent>;
|
||||
let component: AccordionGroupComponent;
|
||||
let element: any;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AccordionGroupComponent
|
||||
],
|
||||
providers: [AccordionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AccordionGroupComponent);
|
||||
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
|
||||
});
|
||||
|
||||
it('should be closed by default', () => {
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let headerToggle = fixture.nativeElement.querySelector('.adf-panel-heading-toggle .material-icons');
|
||||
expect(headerToggle.innerText).toEqual('expand_more');
|
||||
let headerText = fixture.nativeElement.querySelector('.adf-panel-heading-text');
|
||||
expect(headerText.innerText).toEqual('Fake Header');
|
||||
let headerIcon = fixture.nativeElement.querySelector('.adf-panel-heading-icon .material-icons');
|
||||
expect(headerIcon.innerText).toEqual('fake-icon');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be open when click', () => {
|
||||
component.isSelected = true;
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
fixture.detectChanges();
|
||||
element.querySelector('#accordion-button').click();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let headerText = fixture.nativeElement.querySelector('.adf-panel-heading-text');
|
||||
expect(headerText.innerText).toEqual('Fake Header');
|
||||
let headerIcon = fixture.nativeElement.querySelector('.adf-panel-heading-icon .material-icons');
|
||||
expect(headerIcon.innerText).toEqual('fake-icon');
|
||||
let headerToggle = fixture.nativeElement.querySelector('.adf-panel-heading-toggle .material-icons');
|
||||
expect(headerToggle.innerText).toEqual('expand_less');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* @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, OnDestroy } from '@angular/core';
|
||||
import { AccordionComponent } from './accordion.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-accordion-group',
|
||||
moduleId: module.id,
|
||||
templateUrl: 'accordion-group.component.html',
|
||||
styleUrls: ['./accordion-group.component.css']
|
||||
|
||||
})
|
||||
export class AccordionGroupComponent implements OnDestroy {
|
||||
private _isOpen: boolean = false;
|
||||
private _isSelected: boolean = false;
|
||||
|
||||
@Input()
|
||||
heading: string;
|
||||
|
||||
@Input()
|
||||
headingIcon: string;
|
||||
|
||||
@Input()
|
||||
set isOpen(value: boolean) {
|
||||
this._isOpen = value;
|
||||
if (value) {
|
||||
this.accordion.closeOthers(this);
|
||||
}
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
return this._isOpen;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set isSelected(value: boolean) {
|
||||
this._isSelected = value;
|
||||
}
|
||||
|
||||
get isSelected() {
|
||||
return this._isSelected;
|
||||
}
|
||||
|
||||
constructor(private accordion: AccordionComponent) {
|
||||
this.accordion.addGroup(this);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.accordion.removeGroup(this);
|
||||
}
|
||||
|
||||
hasHeadingIcon() {
|
||||
return this.headingIcon ? true : false;
|
||||
}
|
||||
|
||||
toggleOpen(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
|
||||
getAccordionIcon(): string {
|
||||
return this.isOpen ? 'expand_less' : 'expand_more';
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { AccordionComponent } from './accordion.component';
|
||||
import { AccordionGroupComponent } from './accordion-group.component';
|
||||
|
||||
describe('AccordionComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<AccordionComponent>;
|
||||
let component: AccordionComponent;
|
||||
let componentGroup1: AccordionGroupComponent;
|
||||
let componentGroup2: AccordionGroupComponent;
|
||||
let componentGroup3: AccordionGroupComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AccordionComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AccordionComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
component.groups = [];
|
||||
});
|
||||
|
||||
it('should create the component', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add the AccordionGroup', () => {
|
||||
component.addGroup(componentGroup1);
|
||||
expect(component.groups.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should close all the other group', () => {
|
||||
componentGroup1 = new AccordionGroupComponent(component);
|
||||
componentGroup2 = new AccordionGroupComponent(component);
|
||||
componentGroup3 = new AccordionGroupComponent(component);
|
||||
componentGroup1.isOpen = false;
|
||||
componentGroup2.isOpen = true;
|
||||
componentGroup3.isOpen = false;
|
||||
|
||||
expect(component.groups[0].isOpen).toBeFalsy();
|
||||
expect(component.groups[1].isOpen).toBeTruthy();
|
||||
expect(component.groups[2].isOpen).toBeFalsy();
|
||||
|
||||
componentGroup1.isOpen = true;
|
||||
|
||||
expect(component.groups[0].isOpen).toBeTruthy();
|
||||
expect(component.groups[1].isOpen).toBeFalsy();
|
||||
expect(component.groups[2].isOpen).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should remove the AccordionGroup', () => {
|
||||
component.addGroup(componentGroup1);
|
||||
component.removeGroup(componentGroup1);
|
||||
expect(component.groups.length).toBe(0);
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,51 @@
|
||||
/*!
|
||||
* @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 } from '@angular/core';
|
||||
import { AccordionGroupComponent } from './accordion-group.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-accordion',
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
host: {
|
||||
'class': 'panel-group'
|
||||
}
|
||||
})
|
||||
export class AccordionComponent {
|
||||
groups: Array<AccordionGroupComponent> = [];
|
||||
|
||||
addGroup(group: AccordionGroupComponent): void {
|
||||
this.groups.push(group);
|
||||
}
|
||||
|
||||
closeOthers(openGroup: AccordionGroupComponent): void {
|
||||
this.groups.forEach((group: AccordionGroupComponent) => {
|
||||
if (group !== openGroup) {
|
||||
group.isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeGroup(group: AccordionGroupComponent): void {
|
||||
const index = this.groups.indexOf(group);
|
||||
if (index !== -1) {
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* @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 {AccordionComponent} from './accordion.component';
|
||||
import {AccordionGroupComponent} from './accordion-group.component';
|
||||
|
||||
export const COLLAPSABLE_DIRECTIVES: [any] = [
|
||||
AccordionComponent,
|
||||
AccordionGroupComponent
|
||||
];
|
@ -17,3 +17,4 @@
|
||||
|
||||
export * from './context-menu/index';
|
||||
export * from './material/index';
|
||||
export * from './collapsable/index';
|
||||
|
@ -190,8 +190,20 @@ export class DataTableDemo {
|
||||
this.data = new ObjectDataTableAdapter(
|
||||
// data
|
||||
[
|
||||
{id: 1, name: 'Name 1', createdBy : { name: 'user'}, createdOn: 123, icon: 'http://example.com/img.png'},
|
||||
{id: 2, name: 'Name 2', createdBy : { name: 'user 2'}, createdOn: 123, icon: 'http://example.com/img.png'}
|
||||
{
|
||||
id: 1,
|
||||
name: 'Name 1',
|
||||
createdBy : { name: 'user'},
|
||||
createdOn: 123,
|
||||
icon: 'http://example.com/img.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Name 2',
|
||||
createdBy : { name: 'user 2'},
|
||||
createdOn: 123,
|
||||
icon: 'http://example.com/img.png'
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -295,6 +307,52 @@ onRowClick(event) {
|
||||
</alfresco-datatable>
|
||||
```
|
||||
|
||||
#### Column Templates
|
||||
|
||||
It is possible assigning a custom column template like the following:
|
||||
|
||||
```html
|
||||
<alfresco-datatable ...>
|
||||
<data-columns>
|
||||
<data-column title="Version" key="properties.cm:versionLabel">
|
||||
<template let-value="value">
|
||||
<span>V. {{value}}</span>
|
||||
</template>
|
||||
</data-column>
|
||||
</data-columns>
|
||||
</alfresco-datatable>
|
||||
```
|
||||
|
||||
Example above shows access to the underlying cell value by binding `value` property to the underlying context `value`:
|
||||
|
||||
```html
|
||||
<template let-value="value">
|
||||
```
|
||||
|
||||
Alternatively you can get access to the entire data context using the following syntax:
|
||||
|
||||
```html
|
||||
<template let-entry="$implicit">
|
||||
```
|
||||
|
||||
That means you are going to create local variable `entry` that is bound to the data context via Angular's special `$implicit` keyword.
|
||||
|
||||
```html
|
||||
<template let-entry="$implicit">
|
||||
<span>V. {{entry.data.getValue(entry.row, entry.col)}}</span>
|
||||
</template>
|
||||
```
|
||||
|
||||
In the second case `entry` variable is holding a reference to the following data context:
|
||||
|
||||
```ts
|
||||
{
|
||||
data: DataTableAdapter,
|
||||
row: DataRow,
|
||||
col: DataColumn
|
||||
}
|
||||
```
|
||||
|
||||
#### rowClick event
|
||||
|
||||
_This event is emitted when user clicks the row._
|
||||
|
@ -47,9 +47,9 @@
|
||||
"material-design-icons": "2.2.3",
|
||||
"material-design-lite": "1.2.1",
|
||||
"ng2-translate": "2.5.0",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-datatable": "1.3.0"
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-alfresco-datatable": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.33",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-alfresco-datatable",
|
||||
"description": "Alfresco Angular2 DataTable Component",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
|
||||
@ -55,10 +55,10 @@
|
||||
"@angular/platform-browser": "2.2.2",
|
||||
"@angular/platform-browser-dynamic": "2.2.2",
|
||||
"@angular/router": "3.2.2",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"core-js": "2.4.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"reflect-metadata": "0.1.10",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
|
@ -34,14 +34,14 @@
|
||||
[class.alfresco-datatable__row--selected]="selectedRow === row"
|
||||
[adf-upload]="allowDropFiles" [adf-upload-data]="row">
|
||||
|
||||
<!-- Actions (right) -->
|
||||
<!-- Actions (left) -->
|
||||
<td *ngIf="actions && actionsPosition === 'left'" class="alfresco-datatable__actions-cell">
|
||||
<button [id]="'action_menu_' + idx" alfresco-mdl-button class="mdl-button--icon" [attr.data-automation-id]="actions_menu">
|
||||
<i class="material-icons">more_vert</i>
|
||||
</button>
|
||||
<ul alfresco-mdl-menu class="mdl-menu--bottom-left"
|
||||
[attr.for]="'action_menu_' + idx">
|
||||
<li class="mdl-menu__item"
|
||||
<li class="mdl-menu__item" [attr.disabled]="action.disabled"
|
||||
[attr.data-automation-id]="action.title"
|
||||
*ngFor="let action of getRowActions(row)"
|
||||
(click)="onExecuteRowAction(row, action)">
|
||||
@ -53,13 +53,14 @@
|
||||
<td *ngIf="multiselect">
|
||||
<md-checkbox [(ngModel)]="row.isSelected"></md-checkbox>
|
||||
</td>
|
||||
<td *ngFor="let col of data.getColumns()" [ngSwitch]="col.type"
|
||||
<td *ngFor="let col of data.getColumns()"
|
||||
class="mdl-data-table__cell--non-numeric non-selectable data-cell {{col.cssClass}}"
|
||||
(click)="onRowClick(row, $event)"
|
||||
(dblclick)="onRowDblClick(row, $event)"
|
||||
[context-menu]="getContextMenuActions(row, col)"
|
||||
[context-menu-enabled]="contextMenu">
|
||||
<div *ngIf="!col.template" class="cell-container">
|
||||
<ng-container [ngSwitch]="col.type">
|
||||
<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)"
|
||||
@ -77,9 +78,13 @@
|
||||
<span *ngSwitchDefault class="cell-value">
|
||||
<!-- empty cell for unknown column type -->
|
||||
</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="col.template" class="cell-container">
|
||||
<template ngFor [ngForOf]="[{ data: data, row: row, col: col }]" [ngForTemplate]="col.template"></template>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="col.template"
|
||||
[ngOutletContext]="{ $implicit: { data: data, row: row, col: col }, value: data.getValue(row, col) }">
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -90,7 +95,7 @@
|
||||
</button>
|
||||
<ul alfresco-mdl-menu class="mdl-menu--bottom-right"
|
||||
[attr.for]="'action_menu_' + idx">
|
||||
<li class="mdl-menu__item"
|
||||
<li class="mdl-menu__item" [attr.disabled]="action.disabled"
|
||||
[attr.data-automation-id]="action.title"
|
||||
*ngFor="let action of getRowActions(row)"
|
||||
(click)="onExecuteRowAction(row, action)">
|
||||
|
@ -414,4 +414,87 @@ describe('DataTable', () => {
|
||||
dataTable.onImageLoadingError(event);
|
||||
expect(event.target.src).toBe(originalSrc);
|
||||
});
|
||||
|
||||
it('should disable the action if there is no permission and disableWithNoPermission true', () => {
|
||||
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[{id: 1, name: 'xyz', allowableOperations: ['create', 'update']}],
|
||||
[]
|
||||
);
|
||||
|
||||
let row = dataTable.data.getRows();
|
||||
let actions = [
|
||||
{
|
||||
disableWithNoPermission: true,
|
||||
permission: 'delete',
|
||||
target: 'folder',
|
||||
title: 'action2'
|
||||
}
|
||||
];
|
||||
|
||||
let updateActions = dataTable.checkPermissions(row[0], actions);
|
||||
expect(updateActions[0].disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should not disable the action if there is no permission and disableWithNoPermission false', () => {
|
||||
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[{id: 1, name: 'xyz', allowableOperations: ['create', 'update']}],
|
||||
[]
|
||||
);
|
||||
|
||||
let row = dataTable.data.getRows();
|
||||
let actions = [
|
||||
{
|
||||
disableWithNoPermission: false,
|
||||
permission: 'delete',
|
||||
target: 'folder',
|
||||
title: 'action2'
|
||||
}
|
||||
];
|
||||
|
||||
let updateActions = dataTable.checkPermissions(row[0], actions);
|
||||
expect(updateActions[0].disabled).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not disable the action if there is the right permission', () => {
|
||||
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[{ id: 1, name: 'xyz', allowableOperations: ['create', 'update', 'delete'] }],
|
||||
[]
|
||||
);
|
||||
|
||||
let row = dataTable.data.getRows();
|
||||
let actions = [
|
||||
{
|
||||
permission: 'delete',
|
||||
target: 'folder',
|
||||
title: 'action2'
|
||||
}
|
||||
];
|
||||
|
||||
let updateActions = dataTable.checkPermissions(row[0], actions);
|
||||
expect(updateActions[0].disabled).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not disable the action if there are no permissions', () => {
|
||||
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[{id: 1, name: 'xyz', allowableOperations: null}],
|
||||
[]
|
||||
);
|
||||
|
||||
let row = dataTable.data.getRows();
|
||||
let actions = [
|
||||
{
|
||||
permission: 'delete',
|
||||
target: 'folder',
|
||||
title: 'action2'
|
||||
}
|
||||
];
|
||||
|
||||
let updateActions = dataTable.checkPermissions(row[0], actions);
|
||||
expect(updateActions[0].disabled).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -236,10 +236,40 @@ export class DataTableComponent implements AfterContentInit, OnChanges {
|
||||
getRowActions(row: DataRow, col: DataColumn): any[] {
|
||||
let event = new DataCellEvent(row, col, []);
|
||||
this.showRowActionsMenu.emit(event);
|
||||
return event.value.actions;
|
||||
|
||||
return this.checkPermissions(row, event.value.actions);
|
||||
}
|
||||
|
||||
checkPermissions(row: DataRow, actions: any[]) {
|
||||
let actionsPermission = [];
|
||||
actions.forEach((action) => {
|
||||
actionsPermission.push(this.checkPermission(row, action));
|
||||
});
|
||||
return actionsPermission;
|
||||
}
|
||||
|
||||
checkPermission(row: DataRow, action) {
|
||||
if (action.permission) {
|
||||
if (this.hasPermissions(row)) {
|
||||
let permissions = row.getValue('allowableOperations');
|
||||
let findPermission = permissions.find(permission => permission === action.permission);
|
||||
if (!findPermission && action.disableWithNoPermission === true) {
|
||||
action.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private hasPermissions(row: DataRow): boolean {
|
||||
return row.getValue('allowableOperations') ? true : false;
|
||||
}
|
||||
|
||||
onExecuteRowAction(row: DataRow, action: any) {
|
||||
if (action.disabled) {
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
this.executeRowAction.emit(new DataRowActionEvent(row, action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,6 @@
|
||||
}
|
||||
|
||||
.mdl-paging__per-page-value {
|
||||
position: absolute;
|
||||
right: 36px;
|
||||
top: 6px;
|
||||
}
|
||||
|
@ -37,6 +37,10 @@
|
||||
Before you start using this development framework, make sure you have installed all required software and done all the
|
||||
necessary configuration [prerequisites](https://github.com/Alfresco/alfresco-ng2-components/blob/master/PREREQUISITES.md).
|
||||
|
||||
## See also
|
||||
|
||||
- [Walkthrough: adding indicators to clearly highlight information about a node](docs/metadata-indicators.md)
|
||||
|
||||
## Install
|
||||
|
||||
Follow the 3 steps below:
|
||||
@ -612,6 +616,61 @@ The following action handlers are provided out-of-box:
|
||||
All system handler names are case-insensitive, `handler="download"` and `handler="DOWNLOAD"`
|
||||
will trigger the same `download` action.
|
||||
|
||||
##### Delete - Show notification message with no permission
|
||||
You can show a notification error when the user don't have the right permission to perform the action.
|
||||
The ContentActionComponent provides the event permissionEvent that is raised when the permission specified in the permission property is missing
|
||||
You can subscribe to this event from your component and use the NotificationService to show a message.
|
||||
|
||||
```html
|
||||
<alfresco-document-list ...>
|
||||
<content-actions>
|
||||
|
||||
<content-action
|
||||
target="document"
|
||||
title="Delete"
|
||||
permission="delete"
|
||||
(permissionEvent)="onPermissionsFailed($event)"
|
||||
handler="delete">
|
||||
</content-action>
|
||||
|
||||
</content-actions>
|
||||
</alfresco-document-list>
|
||||
|
||||
|
||||
export class MyComponent {
|
||||
|
||||
onPermissionsFailed(event: any) {
|
||||
this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
##### Delete - Disable button checking the permission
|
||||
You can easily disable a button when the user doesn't own the permission to perform the action related to the button.
|
||||
The ContentActionComponent provides the property permission that must contain the permission to check and a property disableWithNoPermission that can be true if
|
||||
you want see the button disabled.
|
||||
|
||||
```html
|
||||
<alfresco-document-list ...>
|
||||
<content-actions>
|
||||
|
||||
<content-action
|
||||
target="document"
|
||||
title="Delete"
|
||||
permission="delete"
|
||||
disableWithNoPermission="true"
|
||||
handler="delete">
|
||||
</content-action>
|
||||
|
||||
</content-actions>
|
||||
</alfresco-document-list>
|
||||
```
|
||||
|
||||

|
||||
|
||||
##### Download
|
||||
|
||||
Initiates download of the corresponding document file.
|
||||
@ -632,7 +691,6 @@ Initiates download of the corresponding document file.
|
||||
|
||||

|
||||
|
||||
|
||||
#### Folder actions
|
||||
|
||||
Folder actions have the same declaration as document actions except ```taget="folder"``` attribute value.
|
||||
@ -725,6 +783,7 @@ DocumentList emits the following events:
|
||||
| `nodeDblClick` | emitted when user double-clicks list node |
|
||||
| `folderChange` | emitted once current display folder has changed |
|
||||
| `preview` | emitted when user acts upon files with either single or double click (depends on `navigation-mode`), recommended for Viewer components integration |
|
||||
| `permissionError` | emitted when user is attempting to create a folder via action menu but it doesn't have the permission to do it |
|
||||
|
||||
## Advanced usage and customization
|
||||
|
||||
|
@ -36,10 +36,10 @@
|
||||
"systemjs": "0.19.27",
|
||||
"zone.js": "0.6.26",
|
||||
"ng2-translate": "2.5.0",
|
||||
"alfresco-js-api": "~1.3.0",
|
||||
"ng2-alfresco-core": "1.3.0",
|
||||
"ng2-alfresco-datatable": "1.3.0",
|
||||
"ng2-alfresco-documentlist": "1.3.0",
|
||||
"alfresco-js-api": "~1.4.0",
|
||||
"ng2-alfresco-core": "1.4.0",
|
||||
"ng2-alfresco-datatable": "1.4.0",
|
||||
"ng2-alfresco-documentlist": "1.4.0",
|
||||
"material-design-icons": "2.2.3",
|
||||
"material-design-lite": "1.2.1",
|
||||
"intl": "^1.2.5"
|
||||
|
After Width: | Height: | Size: 133 KiB |
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 100 KiB |
@ -0,0 +1,132 @@
|
||||
# Walkthrough: adding indicators to clearly highlight information about a node
|
||||
|
||||
Every node object in the document list holds metadata information.
|
||||
All metadata is stored inside `properties` property.
|
||||
|
||||
Here's an example of basic image-related metadata fetched from the server:
|
||||
|
||||

|
||||
|
||||
## Custom column template
|
||||
|
||||
```html
|
||||
<alfresco-document-list ...>
|
||||
<data-columns>
|
||||
<data-column key="properties" [sortable]="false">
|
||||
<template let-value="value">
|
||||
<adf-metadata-icons [metadata]="value">
|
||||
</adf-metadata-icons>
|
||||
</template>
|
||||
</data-column>
|
||||
...
|
||||
</data-columns>
|
||||
</alfresco-document-list>
|
||||
```
|
||||
|
||||
We are going to declare a column and bind its value to the entire `properties` object of the underlying node. The column will be using our custom `<adf-metadata-icons>` component to display icons based on metadata state.
|
||||
|
||||
## MetadataIconsComponent component
|
||||
|
||||
Let's create a simple `MetadataIconsComponent` component with a selector set to `adf-metadata-icons` as shown below:
|
||||
|
||||
```ts
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-metadata-icons',
|
||||
template: `
|
||||
<div *ngIf="metadata">
|
||||
<!-- render UI based on metadata -->
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class MetadataIconsComponent {
|
||||
|
||||
@Input()
|
||||
metadata: any;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The component will expose a `metadata` property we can use from the outside and eventually bind data to similar to the following:
|
||||
|
||||
```html
|
||||
<adf-metadata-icons [metadata]="nodeMetadata"></adf-metadata-icons>
|
||||
```
|
||||
|
||||
As you have seen earlier the DataColumn binds to `properties` property of the node, and maps the runtime value as the `value` local variable within the template.
|
||||
Next we propagate the `value` reference to the `<adf-metadata-icons>` component as `metadata` property.
|
||||
|
||||
```html
|
||||
<data-column key="properties" [sortable]="false">
|
||||
<template let-value="value">
|
||||
<adf-metadata-icons [metadata]="value"></adf-metadata-icons>
|
||||
</template>
|
||||
</data-column>
|
||||
```
|
||||
|
||||
So once rendered our component will automatically has access to entire set of node metadata. Let's build some visualization of the `cm:versionLabel` propery.
|
||||
|
||||
For demonstration purposes we are going to display several icons if underlying node has version `2.0`, and just a plain text version value for all other versions.
|
||||
|
||||
```html
|
||||
<div *ngIf="metadata">
|
||||
<ng-container *ngIf="metadata['cm:versionLabel'] === '2.0'">
|
||||
<md-icon>portrait</md-icon>
|
||||
<md-icon>photo_filter</md-icon>
|
||||
<md-icon>rotate_90_degrees_ccw</md-icon>
|
||||
</ng-container>
|
||||
<div *ngIf="metadata['cm:versionLabel'] !== '2.0'">
|
||||
{{metadata['cm:versionLabel']}}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Note: For a list of the icons that can be used with `<md-icon>` component please refer to this resource: [material.io/icons](https://material.io/icons/)
|
||||
|
||||
## Testing component
|
||||
|
||||
You will need to enable `versioning` feature for the Document List to be able uploading multiple versions of the file instead of renaming duplicates.
|
||||
|
||||
Drag and drop any image file to upload it and ensure it has `1.0` displayed in the column:
|
||||
|
||||

|
||||
|
||||
Now drop the same file again to upload a new version of the file.
|
||||
You should now see icons instead of version label.
|
||||
|
||||

|
||||
|
||||
You can see on the screnshot above that only files with version `2.0` got extra icons.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The full source code of the component can be found below:
|
||||
|
||||
```ts
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-metadata-icons',
|
||||
template: `
|
||||
<div *ngIf="metadata">
|
||||
<ng-container *ngIf="metadata['cm:versionLabel'] === '2.0'">
|
||||
<md-icon>portrait</md-icon>
|
||||
<md-icon>photo_filter</md-icon>
|
||||
<md-icon>rotate_90_degrees_ccw</md-icon>
|
||||
</ng-container>
|
||||
<div *ngIf="metadata['cm:versionLabel'] !== '2.0'">
|
||||
{{metadata['cm:versionLabel']}}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class MetadataIconsComponent {
|
||||
|
||||
@Input()
|
||||
metadata: any;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
You can use this idea to build more complex indication experience based on the actual metdata state.
|