-
-
Cancel
-
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.COMPLETE' | translate }}
-
-
{{ 'ADF_CLOUD_TASK_HEADER.BUTTON.CLAIM' | translate }}
-
-
{{ 'ADF_CLOUD_TASK_HEADER.BUTTON.RELEASE' | translate }}
+
+
+
+
+ Cancel
+ {{ 'ADF_TASK_LIST.DETAILS.BUTTON.COMPLETE' | translate }}
+
+ {{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }}
+
+ {{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
+
+
+
+
+
+
-
-
-
-
-
\ No newline at end of file
+
diff --git a/demo-shell/src/app/components/cloud/task-details-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/task-details-cloud-demo.component.ts
index 25f72933b2..95518ed986 100644
--- a/demo-shell/src/app/components/cloud/task-details-cloud-demo.component.ts
+++ b/demo-shell/src/app/components/cloud/task-details-cloud-demo.component.ts
@@ -17,7 +17,8 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
-import { TaskDetailsCloudModel, TaskCloudService } from '@alfresco/adf-process-services-cloud';
+import { TaskDetailsCloudModel, TaskCloudService, UploadCloudWidgetComponent } from '@alfresco/adf-process-services-cloud';
+import { NotificationService, FormRenderingService } from '@alfresco/adf-core';
@Component({
templateUrl: './task-details-cloud-demo.component.html',
@@ -33,7 +34,9 @@ export class TaskDetailsCloudDemoComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router,
- private taskCloudService: TaskCloudService
+ private formRenderingService: FormRenderingService,
+ private taskCloudService: TaskCloudService,
+ private notificationService: NotificationService
) {
this.route.params.subscribe((params) => {
this.taskId = params.taskId;
@@ -41,6 +44,8 @@ export class TaskDetailsCloudDemoComponent implements OnInit {
this.route.parent.params.subscribe((params) => {
this.appName = params.appName;
});
+ this.formRenderingService.setComponentTypeResolver('upload', () => UploadCloudWidgetComponent, true);
+
}
ngOnInit() {
@@ -59,7 +64,7 @@ export class TaskDetailsCloudDemoComponent implements OnInit {
}
canCompleteTask(): boolean {
- return this.taskDetails && this.taskCloudService.canCompleteTask(this.taskDetails);
+ return this.taskDetails && !this.taskDetails.formKey && this.taskCloudService.canCompleteTask(this.taskDetails);
}
canClaimTask(): boolean {
@@ -70,6 +75,10 @@ export class TaskDetailsCloudDemoComponent implements OnInit {
return this.taskDetails && this.taskCloudService.canUnclaimTask(this.taskDetails);
}
+ hasTaskForm(): boolean {
+ return this.taskDetails && this.taskDetails.formKey;
+ }
+
goBack() {
this.router.navigate([`/cloud/${this.appName}/`]);
}
@@ -85,4 +94,12 @@ export class TaskDetailsCloudDemoComponent implements OnInit {
onClaimTask() {
this.goBack();
}
+
+ onTaskCompleted() {
+ this.goBack();
+ }
+
+ onFormSaved() {
+ this.notificationService.openSnackMessage('Task has been saved successfully');
+ }
}
diff --git a/demo-shell/src/app/components/form/form-list.component.ts b/demo-shell/src/app/components/form/form-list.component.ts
index 5bc660b9e8..4ad54895e7 100644
--- a/demo-shell/src/app/components/form/form-list.component.ts
+++ b/demo-shell/src/app/components/form/form-list.component.ts
@@ -16,7 +16,8 @@
*/
import { Component, ViewChild } from '@angular/core';
-import { FormComponent, FormModel, FormService, LogService, FormOutcomeEvent } from '@alfresco/adf-core';
+import { FormModel, FormService, LogService, FormOutcomeEvent } from '@alfresco/adf-core';
+import { FormComponent } from '@alfresco/adf-process-services';
@Component({
selector: 'app-form-list',
diff --git a/demo-shell/src/app/services/in-memory-form.service.ts b/demo-shell/src/app/services/in-memory-form.service.ts
index 892b1e8d39..fe015bd834 100644
--- a/demo-shell/src/app/services/in-memory-form.service.ts
+++ b/demo-shell/src/app/services/in-memory-form.service.ts
@@ -74,7 +74,7 @@ export class InMemoryFormService extends FormService {
if (!json.fields) {
form.outcomes = [
new FormOutcomeModel(form, {
- id: '$custom',
+ id: '$save',
name: FormOutcomeModel.SAVE_ACTION,
isSystem: true
})
diff --git a/docs/docassets/demo-cloud.form.json b/docs/docassets/demo-cloud.form.json
new file mode 100644
index 0000000000..0c3c494c15
--- /dev/null
+++ b/docs/docassets/demo-cloud.form.json
@@ -0,0 +1,665 @@
+{
+ "formRepresentation": {
+ "id": "form-with-all-fields",
+ "name": "Form with all fields",
+ "description": "",
+ "version": 0,
+ "formDefinition": {
+ "tabs": [],
+ "fields": [
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "26b10e64-0403-4686-a75b-0d45279ce3a8",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "text1",
+ "name": "Text1",
+ "type": "text",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "minLength": 0,
+ "maxLength": 0,
+ "minValue": null,
+ "maxValue": null,
+ "regexPattern": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "text2",
+ "name": "Text2",
+ "type": "text",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "minLength": 0,
+ "maxLength": 0,
+ "minValue": null,
+ "maxValue": null,
+ "regexPattern": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "69c1390a-8d8d-423c-8efb-8e43401efa42",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "multilinetext1",
+ "name": "Multiline text1",
+ "type": "multi-line-text",
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "minLength": 0,
+ "maxLength": 0,
+ "regexPattern": null,
+ "required": false,
+ "readOnly": false,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "multilinetext2",
+ "name": "Multiline text2",
+ "type": "multi-line-text",
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "minLength": 0,
+ "maxLength": 0,
+ "regexPattern": null,
+ "required": false,
+ "readOnly": false,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "df046463-2d65-4388-9ee1-0e1517985215",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "number1",
+ "overrideId": false,
+ "name": "Number1",
+ "type": "integer",
+ "colspan": 1,
+ "placeholder": null,
+ "readOnly": false,
+ "minValue": null,
+ "maxValue": null,
+ "required": false,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "number2",
+ "overrideId": false,
+ "name": "Number2",
+ "type": "integer",
+ "colspan": 1,
+ "placeholder": null,
+ "readOnly": false,
+ "minValue": null,
+ "maxValue": null,
+ "required": false,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "9672cc7b-1959-49c9-96be-3816e57bdfc1",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "checkbox1",
+ "name": "Checkbox1",
+ "type": "boolean",
+ "required": false,
+ "readOnly": false,
+ "colspan": 1,
+ "overrideId": false,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "checkbox2",
+ "name": "Checkbox2",
+ "type": "boolean",
+ "required": false,
+ "readOnly": false,
+ "colspan": 1,
+ "overrideId": false,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "054d193e-a899-4494-9a3e-b489315b7d57",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "dropdown1",
+ "name": "Dropdown1",
+ "type": "dropdown",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "optionType": "manual",
+ "options": [],
+ "endpoint": null,
+ "requestHeaders": null,
+ "restUrl": null,
+ "restResponsePath": null,
+ "restIdProperty": null,
+ "restLabelProperty": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "dropdown2",
+ "name": "Dropdown2",
+ "type": "dropdown",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "optionType": "manual",
+ "options": [],
+ "endpoint": null,
+ "requestHeaders": null,
+ "restUrl": null,
+ "restResponsePath": null,
+ "restIdProperty": null,
+ "restLabelProperty": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "1f8f0b66-e022-4667-91b4-bbbf2ddc36fb",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "amount1",
+ "name": "Amount1",
+ "type": "amount",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": "123",
+ "minValue": null,
+ "maxValue": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ },
+ "enableFractions": false,
+ "currency": "$"
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "amount2",
+ "name": "Amount2",
+ "type": "amount",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": "123",
+ "minValue": null,
+ "maxValue": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ },
+ "enableFractions": false,
+ "currency": "$"
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "541a368b-67ee-4a7c-ae7e-232c050b9e24",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "date1",
+ "name": "Date1",
+ "type": "date",
+ "overrideId": false,
+ "required": false,
+ "readOnly": false,
+ "colspan": 1,
+ "placeholder": null,
+ "minValue": null,
+ "maxValue": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ },
+ "dateDisplayFormat": "D-M-YYYY"
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "date2",
+ "name": "Date2",
+ "type": "date",
+ "overrideId": false,
+ "required": false,
+ "readOnly": false,
+ "colspan": 1,
+ "placeholder": null,
+ "minValue": null,
+ "maxValue": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ },
+ "dateDisplayFormat": "D-M-YYYY"
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "e79cb7e2-3dc1-4c79-8158-28662c28a9f3",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "radiobuttons1",
+ "name": "Radio buttons1",
+ "type": "radio-buttons",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "optionType": "manual",
+ "options": [
+ {
+ "id": "option_1",
+ "name": "Option 1"
+ },
+ {
+ "id": "option_2",
+ "name": "Option 2"
+ }
+ ],
+ "endpoint": null,
+ "requestHeaders": null,
+ "restUrl": null,
+ "restResponsePath": null,
+ "restIdProperty": null,
+ "restLabelProperty": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "radiobuttons2",
+ "name": "Radio buttons2",
+ "type": "radio-buttons",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "optionType": "manual",
+ "options": [
+ {
+ "id": "option_1",
+ "name": "Option 1"
+ },
+ {
+ "id": "option_2",
+ "name": "Option 2"
+ }
+ ],
+ "endpoint": null,
+ "requestHeaders": null,
+ "restUrl": null,
+ "restResponsePath": null,
+ "restIdProperty": null,
+ "restLabelProperty": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "7c01ed35-be86-4be7-9c28-ed640a5a2ae1",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "AttachFileFieldRepresentation",
+ "id": "attachfile1",
+ "name": "Attach file1",
+ "type": "upload",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2,
+ "fileSource": {
+ "serviceId": "all-file-sources",
+ "name": "All file sources"
+ },
+ "multiple": false,
+ "link": false
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "AttachFileFieldRepresentation",
+ "id": "attachfile2",
+ "name": "Attach file2",
+ "type": "upload",
+ "value": null,
+ "required": false,
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "placeholder": null,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2,
+ "fileSource": {
+ "serviceId": "all-file-sources",
+ "name": "All file sources"
+ },
+ "multiple": false,
+ "link": false
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "07b13b96-d469-4a1e-8a9a-9bb957c68869",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "displayvalue1",
+ "name": "Display value1",
+ "type": "readonly",
+ "value": "No field selected",
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2,
+ "field": {
+ "id": "displayvalue",
+ "name": "Display value",
+ "type": "text"
+ }
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "displayvalue2",
+ "name": "Display value2",
+ "type": "readonly",
+ "value": "No field selected",
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2,
+ "field": {
+ "id": "displayvalue",
+ "name": "Display value",
+ "type": "text"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "fieldType": "ContainerRepresentation",
+ "id": "1576ef25-c842-494c-ab84-265a1e3bf68d",
+ "name": "Label",
+ "type": "container",
+ "tab": null,
+ "numberOfColumns": 2,
+ "fields": {
+ "1": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "displaytext1",
+ "name": "Display text1",
+ "type": "readonly-text",
+ "value": "Display text as part of the form",
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ],
+ "2": [
+ {
+ "fieldType": "FormFieldRepresentation",
+ "id": "displaytext2",
+ "name": "Display text2",
+ "type": "readonly-text",
+ "value": "Display text as part of the form",
+ "readOnly": false,
+ "overrideId": false,
+ "colspan": 1,
+ "visibilityCondition": null,
+ "params": {
+ "existingColspan": 1,
+ "maxColspan": 2
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "outcomes": [],
+ "javascriptEvents": [],
+ "className": "",
+ "style": "",
+ "customFieldTemplates": {},
+ "metadata": {},
+ "variables": [
+ {
+ "name": "FormVarStr",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "FormVarInt",
+ "type": "integer",
+ "value": ""
+ },
+ {
+ "name": "FormVarBool",
+ "type": "boolean",
+ "value": ""
+ },
+ {
+ "name": "FormVarDate",
+ "type": "date",
+ "value": ""
+ },
+ {
+ "name": "NewVar",
+ "type": "string",
+ "value": ""
+ }
+ ],
+ "customFieldsValueInfo": {},
+ "gridsterForm": false
+ }
+ },
+ "processScopeIdentifiers": []
+}
diff --git a/docs/process-services-cloud/components/form-cloud.component.md b/docs/process-services-cloud/components/form-cloud.component.md
new file mode 100644
index 0000000000..820ce4e62a
--- /dev/null
+++ b/docs/process-services-cloud/components/form-cloud.component.md
@@ -0,0 +1,262 @@
+---
+Title: Form component
+Added: v3.2.0
+Status: Active
+Last reviewed: 2019-04-01
+---
+
+# [Form cloud component](../../../lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts "Defined in form-cloud.component.ts")
+
+Shows a [`form`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) from Process Services
+
+## Contents
+
+- [Basic Usage](#basic-usage)
+ - [Empty form template](#empty-form-template)
+- [Class members](#class-members)
+ - [Properties](#properties)
+ - [Events](#events)
+- [Details](#details)
+ - [Displaying a form](#displaying-a-form)
+ - [Controlling outcome execution behaviour](#controlling-outcome-execution-behaviour)
+ - [Field Validators](#field-validators)
+ - [Common scenarios](#common-scenarios)
+- [See also](#see-also)
+
+## Basic Usage
+
+```html
+
+
+```
+
+### Empty form template
+
+The template defined inside `empty-form` will be shown when no form definition is found:
+
+```html
+
+
+
+
Empty form
+
+
+
+```
+
+## Class members
+
+### Properties
+
+| Name | Type | Default value | Description |
+| ---- | ---- | ------------- | ----------- |
+| appName | `string` | | App id to fetch corresponding form and values. |
+| taskId | `string` | | Task id to fetch corresponding form and values. |
+| form | [`FormCloudModel`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) | | Underlying [form model](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) instance. |
+| formId | `string` | | The id of the form definition to load and display with custom values. |
+| data | [`TaskVariableCloud[]`](../../../lib/process-services-cloud/src/lib/form/models/task-variable.model.ts) | | Custom form values map to be used with the rendered form. |
+| disableCompleteButton | `boolean` | false | If true then the `Complete` outcome button is shown but it will be disabled. |
+| disableStartProcessButton | `boolean` | false | If true then the `Start Process` outcome button is shown but it will be disabled. |
+| fieldValidators | [`FormFieldValidator`](../../../lib/core/form/components/widgets/core/form-field-validator.ts)`[]` | \[] | Contains a list of form field validator instances. |
+| readOnly | `boolean` | false | Toggle readonly state of the form. Forces all form widgets to render as readonly if enabled. |
+| showCompleteButton | `boolean` | true | Toggle rendering of the `Complete` outcome button. |
+| showDebugButton | `boolean` | false | Toggle debug options. |
+| showRefreshButton | `boolean` | true | Toggle rendering of the `Refresh` button. |
+| showSaveButton | `boolean` | true | Toggle rendering of the `Save` outcome button. |
+| showTitle | `boolean` | true | Toggle rendering of the form title. |
+| showValidationIcon | `boolean` | true | Toggle rendering of the validation icon next to the form title. |
+
+
+### Events
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`
` | Emitted when any error occurs. |
+| executeOutcome | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormOutcomeEvent`](../../../lib/core/form/components/widgets/core/form-outcome-event.model.ts)`>` | Emitted when any outcome is executed. Default behaviour can be prevented via `event.preventDefault()`. |
+| formCompleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudModel`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts)`>` | Emitted when the form is submitted with the `Complete` outcome. |
+| formDataRefreshed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudModel`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts)`>` | Emitted when form values are refreshed due to a data property change. |
+| formError | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormFieldModel`](../../core/models/form-field.model.md)`[]>` | Emitted when the supplied form values have a validation error. |
+| formLoaded | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudModel`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts)`>` | Emitted when the form is loaded or reloaded. |
+| formSaved | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloudModel`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts)`>` | Emitted when the form is submitted with the `Save` or custom outcomes. |
+
+## Details
+
+All `formXXX` events receive a [`FormCloudModel`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) instance as their argument:
+
+**MyView.component.html**
+
+```html
+
+
+```
+
+**MyView.component.ts**
+
+```ts
+onFormSaved(form: FormCloudModel) {
+ console.log(form);
+}
+```
+
+### Displaying a form
+
+There are various ways to display a form. The common scenarios are detailed below.
+
+#### Displaying a form instance by task id
+
+```html
+
+
+```
+
+For an existing Task both the form and its values will be fetched and displayed.
+
+#### Displaying a form definition by form id
+
+```html
+
+
+```
+
+In this case, only the form definition will be fetched.
+
+
+### Controlling outcome execution behaviour
+
+In unusual circumstances, you may need to take complete control of form outcome execution.
+You can do this by implementing the `executeOutcome` event, which is emitted for both system
+outcomes and custom ones.
+
+Note that by default, the code in your `executeOutcome` handler is executed _before_ the default
+behavior but you can switch the default behavior off using `event.preventDefault()`.
+You might want to do this, for example, to provide custom form validation or to show a summary
+of the form validation before it is submitted.
+
+**MyView.component.html**
+
+```html
+
+
+```
+
+**MyView.component.ts**
+
+```ts
+import { FormOutcomeEvent } from '@alfresco/adf-core';
+
+export class MyView {
+
+ validateForm(event: FormOutcomeEvent) {
+ let outcome = event.outcome;
+
+ // you can also get additional properties of outcomes
+ // if you defined them within outcome definition
+
+ if (outcome) {
+ let form = outcome.form;
+ if (form) {
+ // check/update the form here
+ event.preventDefault();
+ }
+ }
+ }
+
+}
+```
+
+There are two other functions that can be very useful when you need to control form outcomes:
+
+- `saveTaskForm()` - Saves the current form
+- `completeTaskForm(outcome?: string)` Saves and completes the form with a given outcome name
+
+### Field Validators
+
+You can supply a set of validator objects to the form using the `fieldValidators`
+property. Each validator implements a check for a particular type of data (eg, a
+date validator might check that the date in the field falls between 1980 and 2017).
+ADF supplies a standard set of validators that handle most common cases but you can
+also implement your own custom validators to replace or extend the set. See the
+[Form Field Validator](../../core/interfaces/form-field-validator.interface.md) interface for full details and examples.
+
+### Common scenarios
+
+#### Rendering a form using form definition JSON
+
+See the [demo-form](../../docassets/demo-cloud.form.json) file for an example of form definition JSON.
+
+The component below (with the JSON assigned to the `formDefinitionJSON` property), shows how a
+form definition is rendered:
+
+```ts
+@Component({
+ selector: 'sample-form',
+ template: ``
+})
+export class SampleFormComponent implements OnInit {
+
+ form: FormCloudModel;
+ formDefinitionJSON: any;
+
+ constructor(private formService: FormService) {
+ }
+
+ ngOnInit() {
+ this.form = this.formService.parseForm(this.formDefinitionJSON);
+ }
+}
+```
+
+#### Customizing the styles of form outcome buttons
+
+You can use normal CSS selectors to style the outcome buttons of your form.
+Every outcome has an CSS id value following a simple pattern:
+
+ adf-cloud-form-OUTCOME_NAME
+
+In the CSS, you can target any outcome ID and change the style as in this example:
+
+```css
+#adf-cloud-form-complete {
+ background-color: blue !important;
+ color: white;
+}
+
+
+#adf-cloud-form-save {
+ background-color: green !important;
+ color: white;
+}
+
+#adf-cloud-form-customoutcome {
+ background-color: yellow !important;
+ color: white;
+}
+```
+
+
+
+
+## See also
+
+- [Form Field Validator interface](../../core/interfaces/form-field-validator.interface.md)
+- [Extensibility](../../user-guide/extensibility.md)
+- [Form rendering service](../../core/services/form-rendering.service.md)
+- [Form field model](../../core/models/form-field.model.md)
+- [Form service](../services/form-cloud.service.md)
diff --git a/docs/process-services-cloud/services/form-cloud.service.md b/docs/process-services-cloud/services/form-cloud.service.md
new file mode 100644
index 0000000000..d5777ba7d9
--- /dev/null
+++ b/docs/process-services-cloud/services/form-cloud.service.md
@@ -0,0 +1,67 @@
+---
+Title: Form service
+Title: Form cloud service
+Added: v3.2.0
+Status: Active
+Last reviewed: 2019-04-02
+---
+
+# [Form cloud service](../../../lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts "Defined in form-cloud.service.ts")
+
+Implements Process Services form methods
+
+## Basic Usage
+
+```ts
+import { FormService } from '@alfresco/adf-core';
+
+@Component(...)
+class MyComponent {
+
+ constructor(formService: FormService) {
+
+}
+```
+
+### Methods
+
+- `parseForm(json: any, data?:`[`TaskVariableCloud,`](../../../lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts)`readOnly: boolean = false):`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)
+ Parses JSON data to create a corresponding [`Form`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) model.
+ - `json` - JSON to create the form
+ - `data` - (Optional) [`Values`](../../../lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts) for the form fields
+ - `readOnly` - Should the form fields be read-only?
+
+- `saveTaskForm(appName: string, taskId: string, formId: string, formValues: FormValues):`[`Observable`](http://reactivex.io/documentation/observable.html)``
+ Saves task [`form`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts).
+ - `appName` - App Name
+ - `taskId` - Task Id
+ - `formId` - Form Id
+ - `formValues` - [`Form Values`](../../../lib/core/form/components/widgets/core/form-values.ts)
+
+- `completeTaskForm(appName: string, taskId: string, formId: string, formValues: FormValues, outcome: string):`[`Observable`](http://reactivex.io/documentation/observable.html)``
+ Completes task [`form`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts)
+ - `appName` - App Name
+ - `taskId` - Task Id
+ - `formId` - Form Id
+ - `formValues` - [`Form Values`](../../../lib/core/form/components/widgets/core/form-values.ts)
+ - `outcome` - (Optional) [`Form`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) Outcome
+
+- `getTaskForm(appName: string, taskId: string):`[`Observable`](http://reactivex.io/documentation/observable.html)``
+ Get form defintion of a task
+ - `appName` - App Name
+ - `taskId` - Task Id
+
+- `getForm(appName: string, formId: string):`[`Observable`](http://reactivex.io/documentation/observable.html)``
+ Get a form definition
+ - `appName` - App Name
+ - `formId` - Form Id
+
+- `getTask(appName: string, taskId: string):`[`Observable`](http://reactivex.io/documentation/observable.html)<[`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/start-task/models/task-details-cloud.model.ts)>
+ Gets details of a task.
+ - `appName` - App Name
+ - `taskId` - Task Id
+
+- `getTaskVariables(appName: string, taskId: string):`[`Observable`](http://reactivex.io/documentation/observable.html)<[`TaskVariableCloud`](../../../lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts)[]>
+ Gets variables of a task.
+ - `appName` - App Name
+ - `taskId` - Task Id
diff --git a/docs/core/components/form.component.md b/docs/process-services/components/form.component.md
similarity index 94%
rename from docs/core/components/form.component.md
rename to docs/process-services/components/form.component.md
index dc5aa913d9..351371144c 100644
--- a/docs/core/components/form.component.md
+++ b/docs/process-services/components/form.component.md
@@ -5,9 +5,9 @@ Status: Active
Last reviewed: 2019-01-16
---
-# [Form component](../../../lib/core/form/components/form.component.ts "Defined in form.component.ts")
+# [Form component](../../../lib/process-services/form/form.component.ts "Defined in form.component.ts")
-Shows a [`Form`](../../../lib/process-services/task-list/models/form.model.ts) from APS
+Shows a [`Form`](../../../lib/core/form/components/widgets/core/form.model.ts) from APS
(See it live: [Form Quickstart](https://embed.plnkr.co/YSLXTqb3DtMhVJSqXKkE/))
@@ -279,7 +279,7 @@ could use this, say, to provide two alternative ways of entering the same inform
up default values that can be edited.
You can implement this in ADF using the `formFieldValueChanged` event of the
-[Form service](../services/form.service.md). For example, if you had a form with a dropdown widget (id: `type`)
+[Form service](../../core/services/form.service.md). For example, if you had a form with a dropdown widget (id: `type`)
and a multiline text (id:`description`), you could synchronize their values as follows:
```ts
@@ -305,7 +305,7 @@ The result should look like the following:
#### Responding to all form events
-Subscribe to the `formEvents` event of the [Form service](../services/form.service.md) to get notification
+Subscribe to the `formEvents` event of the [Form service](../../core/services/form.service.md) to get notification
of all form events:
```ts
@@ -361,8 +361,8 @@ Also, don't forget to set the `providers` property to `ALL` in the `app.config.j
## See also
-- [Form Field Validator interface](../interfaces/form-field-validator.interface.md)
+- [Form Field Validator interface](../../core/interfaces/form-field-validator.interface.md)
- [Extensibility](../../user-guide/extensibility.md)
-- [Form rendering service](../services/form-rendering.service.md)
-- [Form field model](../models/form-field.model.md)
-- [Form service](../services/form.service.md)
+- [Form rendering service](../../core/services/form-rendering.service.md)
+- [Form field model](../../core/models/form-field.model.md)
+- [Form service](../../core/services/form.service.md)
diff --git a/lib/core/core.module.ts b/lib/core/core.module.ts
index 742a3f3eb9..4fc25a86d9 100644
--- a/lib/core/core.module.ts
+++ b/lib/core/core.module.ts
@@ -35,7 +35,7 @@ import { HostSettingsModule } from './settings/host-settings.module';
import { ToolbarModule } from './toolbar/toolbar.module';
import { UserInfoModule } from './userinfo/userinfo.module';
import { ViewerModule } from './viewer/viewer.module';
-import { FormModule } from './form/form.module';
+import { FormBaseModule } from './form/form-base.module';
import { SidenavLayoutModule } from './layout/layout.module';
import { CommentsModule } from './comments/comments.module';
import { ButtonsMenuModule } from './buttons-menu/buttons-menu.module';
@@ -75,7 +75,7 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
ToolbarModule,
ContextMenuModule,
CardViewModule,
- FormModule,
+ FormBaseModule,
CommentsModule,
LoginModule,
LanguageMenuModule,
@@ -106,7 +106,7 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
ToolbarModule,
ContextMenuModule,
CardViewModule,
- FormModule,
+ FormBaseModule,
CommentsModule,
LoginModule,
LanguageMenuModule,
diff --git a/lib/core/form/components/form-base.component.ts b/lib/core/form/components/form-base.component.ts
new file mode 100644
index 0000000000..88d355932c
--- /dev/null
+++ b/lib/core/form/components/form-base.component.ts
@@ -0,0 +1,213 @@
+/*!
+ * @license
+ * Copyright 2019 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 { FormBaseModel } from './form-base.model';
+import { FormOutcomeModel, FormFieldValidator, FormFieldModel, FormOutcomeEvent } from './widgets';
+import { EventEmitter, Input, Output } from '@angular/core';
+
+export abstract class FormBaseComponent {
+
+ static SAVE_OUTCOME_ID: string = '$save';
+ static COMPLETE_OUTCOME_ID: string = '$complete';
+ static START_PROCESS_OUTCOME_ID: string = '$startProcess';
+ static CUSTOM_OUTCOME_ID: string = '$custom';
+ static COMPLETE_BUTTON_COLOR: string = 'primary';
+ static COMPLETE_OUTCOME_NAME: string = 'COMPLETE';
+
+ /** Path of the folder where the metadata will be stored. */
+ @Input()
+ path: string;
+
+ /** Name to assign to the new node where the metadata are stored. */
+ @Input()
+ nameNode: string;
+
+ /** Toggle rendering of the form title. */
+ @Input()
+ showTitle: boolean = true;
+
+ /** Toggle rendering of the `Complete` outcome button. */
+ @Input()
+ showCompleteButton: boolean = true;
+
+ /** If true then the `Complete` outcome button is shown but it will be disabled. */
+ @Input()
+ disableCompleteButton: boolean = false;
+
+ /** If true then the `Start Process` outcome button is shown but it will be disabled. */
+ @Input()
+ disableStartProcessButton: boolean = false;
+
+ /** Toggle rendering of the `Save` outcome button. */
+ @Input()
+ showSaveButton: boolean = true;
+
+ /** Toggle readonly state of the form. Forces all form widgets to render as readonly if enabled. */
+ @Input()
+ readOnly: boolean = false;
+
+ /** Toggle rendering of the `Refresh` button. */
+ @Input()
+ showRefreshButton: boolean = true;
+
+ /** Toggle rendering of the validation icon next to the form title. */
+ @Input()
+ showValidationIcon: boolean = true;
+
+ /** Contains a list of form field validator instances. */
+ @Input()
+ fieldValidators: FormFieldValidator[] = [];
+
+ /** Emitted when the supplied form values have a validation error. */
+ @Output()
+ formError: EventEmitter = new EventEmitter();
+
+ /** Emitted when any outcome is executed. Default behaviour can be prevented
+ * via `event.preventDefault()`.
+ */
+ @Output()
+ executeOutcome: EventEmitter = new EventEmitter();
+
+ /**
+ * Emitted when any error occurs.
+ */
+ @Output()
+ error: EventEmitter = new EventEmitter();
+
+ form: FormBaseModel;
+
+ getParsedFormDefinition(): FormBaseComponent {
+ return this;
+ }
+
+ hasForm(): boolean {
+ return this.form ? true : false;
+ }
+
+ isTitleEnabled(): boolean {
+ let titleEnabled = false;
+ if (this.showTitle && this.form) {
+ titleEnabled = true;
+ }
+ return titleEnabled;
+ }
+
+ getColorForOutcome(outcomeName: string): string {
+ return outcomeName === FormBaseComponent.COMPLETE_OUTCOME_NAME ? FormBaseComponent.COMPLETE_BUTTON_COLOR : '';
+ }
+
+ isOutcomeButtonEnabled(outcome: FormOutcomeModel): boolean {
+ if (this.form.readOnly) {
+ return false;
+ }
+
+ if (outcome) {
+ // Make 'Save' button always available
+ if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
+ return true;
+ }
+ if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
+ return this.disableCompleteButton ? false : this.form.isValid;
+ }
+ if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
+ return this.disableStartProcessButton ? false : this.form.isValid;
+ }
+ return this.form.isValid;
+ }
+ return false;
+ }
+
+ isOutcomeButtonVisible(outcome: FormOutcomeModel, isFormReadOnly: boolean): boolean {
+ if (outcome && outcome.name) {
+ if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
+ return this.showCompleteButton;
+ }
+ if (isFormReadOnly) {
+ return outcome.isSelected;
+ }
+ if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
+ return this.showSaveButton;
+ }
+ if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Invoked when user clicks outcome button.
+ * @param outcome Form outcome model
+ */
+ onOutcomeClicked(outcome: FormOutcomeModel): boolean {
+ if (!this.readOnly && outcome && this.form) {
+
+ if (!this.onExecuteOutcome(outcome)) {
+ return false;
+ }
+
+ if (outcome.isSystem) {
+ if (outcome.id === FormBaseComponent.SAVE_OUTCOME_ID) {
+ this.saveTaskForm();
+ return true;
+ }
+
+ if (outcome.id === FormBaseComponent.COMPLETE_OUTCOME_ID) {
+ this.completeTaskForm();
+ return true;
+ }
+
+ if (outcome.id === FormBaseComponent.START_PROCESS_OUTCOME_ID) {
+ this.completeTaskForm();
+ return true;
+ }
+
+ if (outcome.id === FormBaseComponent.CUSTOM_OUTCOME_ID) {
+ this.onTaskSaved(this.form);
+ this.storeFormAsMetadata();
+ return true;
+ }
+ } else {
+ // Note: Activiti is using NAME field rather than ID for outcomes
+ if (outcome.name) {
+ this.onTaskSaved(this.form);
+ this.completeTaskForm(outcome.name);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ handleError(err: any): any {
+ this.error.emit(err);
+ }
+
+ abstract onRefreshClicked();
+
+ abstract saveTaskForm();
+
+ abstract completeTaskForm(outcome?: string);
+
+ protected abstract onTaskSaved(form: FormBaseModel);
+
+ protected abstract storeFormAsMetadata();
+
+ protected abstract onExecuteOutcome(outcome: FormOutcomeModel);
+}
diff --git a/lib/core/form/components/form-base.model.ts b/lib/core/form/components/form-base.model.ts
new file mode 100644
index 0000000000..73731d988d
--- /dev/null
+++ b/lib/core/form/components/form-base.model.ts
@@ -0,0 +1,84 @@
+/*!
+ * @license
+ * Copyright 2019 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 { FormValues } from './widgets/core/form-values';
+import { TabModel } from './widgets/core/tab.model';
+import { FormWidgetModel } from './widgets/core/form-widget.model';
+import { FormOutcomeModel } from './widgets/core/form-outcome.model';
+import { FormFieldModel } from './widgets/core/form-field.model';
+import { ContainerModel } from './widgets/core/container.model';
+
+export abstract class FormBaseModel {
+
+ static UNSET_TASK_NAME: string = 'Nameless task';
+ static SAVE_OUTCOME: string = '$save';
+ static COMPLETE_OUTCOME: string = '$complete';
+ static START_PROCESS_OUTCOME: string = '$startProcess';
+
+ json: any;
+ isValid: boolean;
+
+ values: FormValues = {};
+ tabs: TabModel[] = [];
+ fields: FormWidgetModel[] = [];
+ outcomes: FormOutcomeModel[] = [];
+
+ className: string;
+ readOnly: boolean = false;
+ taskName;
+
+ hasTabs(): boolean {
+ return this.tabs && this.tabs.length > 0;
+ }
+
+ hasFields(): boolean {
+ return this.fields && this.fields.length > 0;
+ }
+
+ hasOutcomes(): boolean {
+ return this.outcomes && this.outcomes.length > 0;
+ }
+
+ getFieldById(fieldId: string): FormFieldModel {
+ return this.getFormFields().find((field) => field.id === fieldId);
+ }
+
+ // TODO: consider evaluating and caching once the form is loaded
+ getFormFields(): FormFieldModel[] {
+ const formFieldModel: FormFieldModel[] = [];
+
+ for (let i = 0; i < this.fields.length; i++) {
+ const field = this.fields[i];
+
+ if (field instanceof ContainerModel) {
+ const container = field;
+ formFieldModel.push(container.field);
+
+ container.field.columns.forEach((column) => {
+ formFieldModel.push(...column.fields);
+ });
+ }
+ }
+
+ return formFieldModel;
+ }
+
+ abstract validateForm();
+ abstract validateField(field: FormFieldModel);
+ abstract onFormFieldChanged(field: FormFieldModel);
+ abstract markAsInvalid();
+}
diff --git a/lib/core/form/components/form-renderer.component.html b/lib/core/form/components/form-renderer.component.html
new file mode 100644
index 0000000000..f142c328dd
--- /dev/null
+++ b/lib/core/form/components/form-renderer.component.html
@@ -0,0 +1,25 @@
+
+
+
diff --git a/lib/core/form/components/form.component.scss b/lib/core/form/components/form-renderer.component.scss
similarity index 98%
rename from lib/core/form/components/form.component.scss
rename to lib/core/form/components/form-renderer.component.scss
index de22f6fad3..b14652d8aa 100644
--- a/lib/core/form/components/form.component.scss
+++ b/lib/core/form/components/form-renderer.component.scss
@@ -1,4 +1,4 @@
-@mixin adf-form-component-theme($theme) {
+@mixin adf-form-renderer-theme($theme) {
$config: mat-typography-config();
$warn: map-get($theme, warn);
diff --git a/lib/core/form/components/form-renderer.component.ts b/lib/core/form/components/form-renderer.component.ts
new file mode 100644
index 0000000000..46c340e9f7
--- /dev/null
+++ b/lib/core/form/components/form-renderer.component.ts
@@ -0,0 +1,38 @@
+/*!
+ * @license
+ * Copyright 2019 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, ViewEncapsulation, Input } from '@angular/core';
+import { FormBaseModel } from './form-base.model';
+
+@Component({
+ selector: 'adf-form-renderer',
+ templateUrl: './form-renderer.component.html',
+ styleUrls: ['./form-renderer.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class FormRendererComponent {
+
+ /** Toggle debug options. */
+ @Input()
+ showDebugButton: boolean = false;
+
+ @Input()
+ formDefinition: FormBaseModel;
+
+ debugMode: boolean;
+
+}
diff --git a/lib/core/form/components/form.component.html b/lib/core/form/components/form.component.html
deleted file mode 100644
index d897b4bc62..0000000000
--- a/lib/core/form/components/form.component.html
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/lib/core/form/components/widgets/core/form-widget.model.ts b/lib/core/form/components/widgets/core/form-widget.model.ts
index 806a86b605..91144c3067 100644
--- a/lib/core/form/components/widgets/core/form-widget.model.ts
+++ b/lib/core/form/components/widgets/core/form-widget.model.ts
@@ -27,7 +27,7 @@ export abstract class FormWidgetModel {
readonly type: string;
readonly tab: string;
- readonly form: FormModel;
+ readonly form: any;
readonly json: any;
constructor(form: FormModel, json: any) {
diff --git a/lib/core/form/components/widgets/core/form.model.ts b/lib/core/form/components/widgets/core/form.model.ts
index e845a66dc6..b2bd10ac23 100644
--- a/lib/core/form/components/widgets/core/form.model.ts
+++ b/lib/core/form/components/widgets/core/form.model.ts
@@ -34,13 +34,9 @@ import {
FORM_FIELD_VALIDATORS,
FormFieldValidator
} from './form-field-validator';
+import { FormBaseModel } from '../../form-base.model';
-export class FormModel {
-
- static UNSET_TASK_NAME: string = 'Nameless task';
- static SAVE_OUTCOME: string = '$save';
- static COMPLETE_OUTCOME: string = '$complete';
- static START_PROCESS_OUTCOME: string = '$startProcess';
+export class FormModel extends FormBaseModel {
readonly id: number;
readonly name: string;
@@ -53,34 +49,14 @@ export class FormModel {
return this._isValid;
}
- className: string;
- readOnly: boolean = false;
- tabs: TabModel[] = [];
- /** Stores root containers */
- fields: FormWidgetModel[] = [];
- outcomes: FormOutcomeModel[] = [];
customFieldTemplates: FormFieldTemplates = {};
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
readonly selectedOutcome: string;
- values: FormValues = {};
processVariables: any;
- readonly json: any;
-
- hasTabs(): boolean {
- return this.tabs && this.tabs.length > 0;
- }
-
- hasFields(): boolean {
- return this.fields && this.fields.length > 0;
- }
-
- hasOutcomes(): boolean {
- return this.outcomes && this.outcomes.length > 0;
- }
-
constructor(json?: any, formValues?: FormValues, readOnly: boolean = false, protected formService?: FormService) {
+ super();
this.readOnly = readOnly;
if (json) {
@@ -156,30 +132,6 @@ export class FormModel {
}
}
- getFieldById(fieldId: string): FormFieldModel {
- return this.getFormFields().find((field) => field.id === fieldId);
- }
-
- // TODO: consider evaluating and caching once the form is loaded
- getFormFields(): FormFieldModel[] {
- const formFieldModel: FormFieldModel[] = [];
-
- for (let i = 0; i < this.fields.length; i++) {
- const field = this.fields[i];
-
- if (field instanceof ContainerModel) {
- const container = field;
- formFieldModel.push(container.field);
-
- container.field.columns.forEach((column) => {
- formFieldModel.push(...column.fields);
- });
- }
- }
-
- return formFieldModel;
- }
-
markAsInvalid() {
this._isValid = false;
}
diff --git a/lib/core/form/form.module.ts b/lib/core/form/form-base.module.ts
similarity index 90%
rename from lib/core/form/form.module.ts
rename to lib/core/form/form-base.module.ts
index d27e909d3e..39c1827bd3 100644
--- a/lib/core/form/form.module.ts
+++ b/lib/core/form/form-base.module.ts
@@ -32,11 +32,10 @@ import { StartFormCustomButtonDirective } from './components/form-custom-button.
import { FormFieldComponent } from './components/form-field/form-field.component';
import { FormListComponent } from './components/form-list.component';
-import { FormComponent } from './components/form.component';
-import { StartFormComponent } from './components/start-form.component';
import { ContentWidgetComponent } from './components/widgets/content/content.widget';
import { WidgetComponent } from './components/widgets/widget.component';
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
+import { FormRendererComponent } from './components/form-renderer.component';
@NgModule({
imports: [
@@ -55,9 +54,8 @@ import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimep
declarations: [
ContentWidgetComponent,
FormFieldComponent,
- FormComponent,
FormListComponent,
- StartFormComponent,
+ FormRendererComponent,
StartFormCustomButtonDirective,
...WIDGET_DIRECTIVES,
...MASK_DIRECTIVE,
@@ -69,12 +67,11 @@ import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimep
exports: [
ContentWidgetComponent,
FormFieldComponent,
- FormComponent,
FormListComponent,
- StartFormComponent,
+ FormRendererComponent,
StartFormCustomButtonDirective,
...WIDGET_DIRECTIVES
]
})
-export class FormModule {
+export class FormBaseModule {
}
diff --git a/lib/core/form/public-api.ts b/lib/core/form/public-api.ts
index 389f6d00c5..c977566a48 100644
--- a/lib/core/form/public-api.ts
+++ b/lib/core/form/public-api.ts
@@ -15,10 +15,10 @@
* limitations under the License.
*/
-export * from './components/form.component';
+export * from './components/form-base.component';
export * from './components/form-list.component';
export * from './components/widgets/content/content.widget';
-export * from './components/start-form.component';
+export * from './components/form-renderer.component';
export * from './components/widgets/index';
export * from './components/widgets/dynamic-table/dynamic-table-row.model';
@@ -32,4 +32,4 @@ export * from './services/widget-visibility.service';
export * from './events/index';
-export * from './form.module';
+export * from './form-base.module';
diff --git a/lib/core/form/services/form.service.ts b/lib/core/form/services/form.service.ts
index 65f5a57d28..77e7205d90 100644
--- a/lib/core/form/services/form.service.ts
+++ b/lib/core/form/services/form.service.ts
@@ -106,7 +106,7 @@ export class FormService {
if (!json.fields) {
form.outcomes = [
new FormOutcomeModel(form, {
- id: '$custom',
+ id: '$save',
name: FormOutcomeModel.SAVE_ACTION,
isSystem: true
})
diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json
index a8169edac3..16807a4fcc 100644
--- a/lib/core/i18n/en.json
+++ b/lib/core/i18n/en.json
@@ -26,6 +26,9 @@
"AT_LEAST_LONG": "Enter at least {{ minLength }} characters",
"NO_LONGER_THAN": "Enter no more than {{ maxLength }} characters"
}
+ },
+ "FORM_RENDERER": {
+ "NAMELESS_TASK": "Nameless task"
}
},
"CORE": {
diff --git a/lib/core/pagination/pagination.component.spec.ts b/lib/core/pagination/pagination.component.spec.ts
index ef48716643..b9505e0e5b 100644
--- a/lib/core/pagination/pagination.component.spec.ts
+++ b/lib/core/pagination/pagination.component.spec.ts
@@ -19,7 +19,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Pagination } from '@alfresco/js-api';
import { PaginationComponent } from './pagination.component';
-import { PaginatedComponent } from './public-api';
+import { PaginatedComponent } from './paginated-component.interface';
import { BehaviorSubject } from 'rxjs';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
diff --git a/lib/core/styles/_index.scss b/lib/core/styles/_index.scss
index 6ac152b51c..4ead278054 100644
--- a/lib/core/styles/_index.scss
+++ b/lib/core/styles/_index.scss
@@ -20,7 +20,7 @@
@import '../viewer/components/pdfViewer-thumbnails.component';
@import '../viewer/components/txtViewer.component';
@import '../viewer/components/imgViewer.component';
-@import '../form/components/form.component';
+@import '../form/components/form-renderer.component';
@import '../layout/components/sidebar-action/sidebar-action-menu.component';
@import '../layout/components/header/header.component';
@import '../comments/comment-list.component';
@@ -54,7 +54,7 @@
@include adf-pdf-thumbnails-theme($theme);
@include adf-image-viewer-theme($theme);
@include adf-text-viewer-theme($theme);
- @include adf-form-component-theme($theme);
+ @include adf-form-renderer-theme($theme);
@include adf-sidebar-action-menu-theme($theme);
@include adf-task-list-comment-list-theme($theme);
@include adf-task-list-comment-theme($theme);
diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.html b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.html
new file mode 100644
index 0000000000..116db6e634
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts
new file mode 100644
index 0000000000..27ef2f9c17
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts
@@ -0,0 +1,753 @@
+/*!
+ * @license
+ * Copyright 2019 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 { SimpleChange } from '@angular/core';
+import { Observable, of, throwError } from 'rxjs';
+import { FormFieldModel, FormFieldTypes, FormOutcomeEvent, FormOutcomeModel, LogService, WidgetVisibilityService } from '@alfresco/adf-core';
+import { FormCloudService } from '../services/form-cloud.service';
+import { FormCloudComponent } from './form-cloud.component';
+import { FormCloud } from '../models/form-cloud.model';
+import { cloudFormMock } from '../mocks/cloud-form.mock';
+
+describe('FormCloudComponent', () => {
+
+ let formService: FormCloudService;
+ let formComponent: FormCloudComponent;
+ let visibilityService: WidgetVisibilityService;
+ let logService: LogService;
+
+ beforeEach(() => {
+ logService = new LogService(null);
+ visibilityService = new WidgetVisibilityService(null, logService);
+ spyOn(visibilityService, 'refreshVisibility').and.stub();
+ formService = new FormCloudService(null, null, logService);
+ formComponent = new FormCloudComponent(formService, visibilityService);
+ });
+
+ it('should check form', () => {
+ expect(formComponent.hasForm()).toBeFalsy();
+ formComponent.form = new FormCloud();
+ expect(formComponent.hasForm()).toBeTruthy();
+ });
+
+ it('should allow title if showTitle is true', () => {
+ const formModel = new FormCloud();
+ formComponent.form = formModel;
+
+ expect(formComponent.showTitle).toBeTruthy();
+ expect(formComponent.isTitleEnabled()).toBeTruthy();
+
+ });
+
+ it('should not allow title if showTitle is false', () => {
+ const formModel = new FormCloud();
+
+ formComponent.form = formModel;
+ formComponent.showTitle = false;
+
+ expect(formComponent.isTitleEnabled()).toBeFalsy();
+ });
+
+ it('should return primary color for complete button', () => {
+ expect(formComponent.getColorForOutcome('COMPLETE')).toBe('primary');
+ });
+
+ it('should not enable outcome button when model missing', () => {
+ expect(formComponent.isOutcomeButtonVisible(null, false)).toBeFalsy();
+ });
+
+ it('should enable custom outcome buttons', () => {
+ const formModel = new FormCloud();
+ formComponent.form = formModel;
+ const outcome = new FormOutcomeModel( formModel, { id: 'action1', name: 'Action 1' });
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
+ });
+
+ it('should allow controlling [complete] button visibility', () => {
+ const formModel = new FormCloud();
+ formComponent.form = formModel;
+ const outcome = new FormOutcomeModel( formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
+
+ formComponent.showSaveButton = true;
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
+
+ formComponent.showSaveButton = false;
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
+ });
+
+ it('should show only [complete] button with readOnly form ', () => {
+ const formModel = new FormCloud();
+ formModel.readOnly = true;
+ formComponent.form = formModel;
+ const outcome = new FormOutcomeModel( formModel, { id: '$complete', name: FormOutcomeModel.COMPLETE_ACTION });
+
+ formComponent.showCompleteButton = true;
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
+ });
+
+ it('should not show [save] button with readOnly form ', () => {
+ const formModel = new FormCloud();
+ formModel.readOnly = true;
+ formComponent.form = formModel;
+ const outcome = new FormOutcomeModel( formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
+
+ formComponent.showSaveButton = true;
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
+ });
+
+ it('should show [custom-outcome] button with readOnly form and selected custom-outcome', () => {
+ const formModel = new FormCloud({formRepresentation: {formDefinition: {selectedOutcome: 'custom-outcome'}}});
+ formModel.readOnly = true;
+ formComponent.form = formModel;
+ let outcome = new FormOutcomeModel( formModel, { id: '$customoutome', name: 'custom-outcome' });
+
+ formComponent.showCompleteButton = true;
+ formComponent.showSaveButton = true;
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
+
+ outcome = new FormOutcomeModel( formModel, { id: '$customoutome2', name: 'custom-outcome2' });
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
+ });
+
+ it('should allow controlling [save] button visibility', () => {
+ const formModel = new FormCloud();
+ formModel.readOnly = false;
+ formComponent.form = formModel;
+ const outcome = new FormOutcomeModel( formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION });
+
+ formComponent.showCompleteButton = true;
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
+
+ formComponent.showCompleteButton = false;
+ expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
+ });
+
+ it('should load form on refresh', () => {
+ spyOn(formComponent, 'loadForm').and.stub();
+
+ formComponent.onRefreshClicked();
+ expect(formComponent.loadForm).toHaveBeenCalled();
+ });
+
+ it('should get task variables if a task form is rendered', () => {
+ spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => {
+ return new Observable((observer) => {
+ observer.next({ formRepresentation: { taskId: currentTaskId }});
+ observer.complete();
+ });
+ });
+
+ spyOn(formService, 'getTaskVariables').and.returnValue(of({}));
+ spyOn(formService, 'getTask').and.callFake((currentTaskId) => {
+ return new Observable((observer) => {
+ observer.next({ formRepresentation: { taskId: currentTaskId }});
+ observer.complete();
+ });
+ });
+ const taskId = '123';
+ const appName = 'test-app';
+
+ formComponent.appName = appName;
+ formComponent.taskId = taskId;
+ formComponent.loadForm();
+
+ expect(formService.getTaskVariables).toHaveBeenCalledWith(appName, taskId);
+ });
+
+ it('should not get task variables and form if task id is not specified', () => {
+ spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => {
+ return new Observable((observer) => {
+ observer.next({ taskId: currentTaskId });
+ observer.complete();
+ });
+ });
+
+ spyOn(formService, 'getTaskVariables').and.returnValue(of({}));
+
+ formComponent.appName = 'test-app';
+ formComponent.taskId = null;
+ formComponent.loadForm();
+
+ expect(formService.getTaskForm).not.toHaveBeenCalled();
+ expect(formService.getTaskVariables).not.toHaveBeenCalled();
+ });
+
+ it('should get form definition by form id on load', () => {
+ spyOn(formComponent, 'getFormById').and.stub();
+
+ const formId = '123';
+ const appName = 'test-app';
+
+ formComponent.appName = appName;
+ formComponent.formId = formId;
+ formComponent.loadForm();
+
+ expect(formComponent.getFormById).toHaveBeenCalledWith(appName, formId);
+ });
+
+ it('should refresh visibility when the form is loaded', () => {
+ spyOn(formService, 'getForm').and.returnValue(of({formRepresentation: {formDefinition: {}}}));
+ const formId = '123';
+ const appName = 'test-app';
+
+ formComponent.appName = appName;
+ formComponent.formId = formId;
+ formComponent.loadForm();
+
+ expect(formService.getForm).toHaveBeenCalledWith(appName, formId);
+ expect(visibilityService.refreshVisibility).toHaveBeenCalled();
+ });
+
+ it('should reload form by task id on binding changes', () => {
+ spyOn(formComponent, 'getFormByTaskId').and.stub();
+ const taskId = '';
+
+ const appName = 'test-app';
+ formComponent.appName = appName;
+ const change = new SimpleChange(null, taskId, true);
+ formComponent.ngOnChanges({ 'taskId': change });
+
+ expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(appName, taskId);
+ });
+
+ it('should reload form definition by form id on binding changes', () => {
+ spyOn(formComponent, 'getFormById').and.stub();
+ const formId = '123';
+ const appName = 'test-app';
+
+ formComponent.appName = appName;
+ const change = new SimpleChange(null, formId, true);
+ formComponent.ngOnChanges({ 'formId': change });
+
+ expect(formComponent.getFormById).toHaveBeenCalledWith(appName, formId);
+ });
+
+ it('should not get form on load', () => {
+ spyOn(formComponent, 'getFormByTaskId').and.stub();
+ spyOn(formComponent, 'getFormById').and.stub();
+
+ formComponent.taskId = null;
+ formComponent.formId = null;
+ formComponent.loadForm();
+
+ expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
+ expect(formComponent.getFormById).not.toHaveBeenCalled();
+ });
+
+ it('should not reload form on unrelated binding changes', () => {
+ spyOn(formComponent, 'getFormByTaskId').and.stub();
+ spyOn(formComponent, 'getFormById').and.stub();
+
+ formComponent.ngOnChanges({ 'tag': new SimpleChange(null, 'hello world', false) });
+
+ expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
+ expect(formComponent.getFormById).not.toHaveBeenCalled();
+ });
+
+ it('should complete form on custom outcome click', () => {
+ const formModel = new FormCloud();
+ const outcomeName = 'Custom Action';
+ const outcome = new FormOutcomeModel( formModel, { id: 'custom1', name: outcomeName });
+
+ let saved = false;
+ formComponent.form = formModel;
+ formComponent.formSaved.subscribe((v) => saved = true);
+ spyOn(formComponent, 'completeTaskForm').and.stub();
+
+ const result = formComponent.onOutcomeClicked(outcome);
+ expect(result).toBeTruthy();
+ expect(saved).toBeTruthy();
+ expect(formComponent.completeTaskForm).toHaveBeenCalledWith(outcomeName);
+ });
+
+ it('should save form on [save] outcome click', () => {
+ const formModel = new FormCloud();
+ const outcome = new FormOutcomeModel( formModel, {
+ id: FormCloudComponent.SAVE_OUTCOME_ID,
+ name: 'Save',
+ isSystem: true
+ });
+
+ formComponent.form = formModel;
+ spyOn(formComponent, 'saveTaskForm').and.stub();
+
+ const result = formComponent.onOutcomeClicked(outcome);
+ expect(result).toBeTruthy();
+ expect(formComponent.saveTaskForm).toHaveBeenCalled();
+ });
+
+ it('should complete form on [complete] outcome click', () => {
+ const formModel = new FormCloud();
+ const outcome = new FormOutcomeModel( formModel, {
+ id: FormCloudComponent.COMPLETE_OUTCOME_ID,
+ name: 'Complete',
+ isSystem: true
+ });
+
+ formComponent.form = formModel;
+ spyOn(formComponent, 'completeTaskForm').and.stub();
+
+ const result = formComponent.onOutcomeClicked(outcome);
+ expect(result).toBeTruthy();
+ expect(formComponent.completeTaskForm).toHaveBeenCalled();
+ });
+
+ it('should emit form saved event on custom outcome click', () => {
+ const formModel = new FormCloud();
+ const outcome = new FormOutcomeModel( formModel, {
+ id: FormCloudComponent.CUSTOM_OUTCOME_ID,
+ name: 'Custom',
+ isSystem: true
+ });
+
+ let saved = false;
+ formComponent.form = formModel;
+ formComponent.formSaved.subscribe((v) => saved = true);
+
+ const result = formComponent.onOutcomeClicked(outcome);
+ expect(result).toBeTruthy();
+ expect(saved).toBeTruthy();
+ });
+
+ it('should do nothing when clicking outcome for readonly form', () => {
+ const formModel = new FormCloud();
+ const outcomeName = 'Custom Action';
+ const outcome = new FormOutcomeModel( formModel, { id: 'custom1', name: outcomeName });
+
+ formComponent.form = formModel;
+ spyOn(formComponent, 'completeTaskForm').and.stub();
+
+ expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
+ formComponent.readOnly = true;
+ expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
+ });
+
+ it('should require outcome model when clicking outcome', () => {
+ formComponent.form = new FormCloud();
+ formComponent.readOnly = false;
+ expect(formComponent.onOutcomeClicked(null)).toBeFalsy();
+ });
+
+ it('should require loaded form when clicking outcome', () => {
+ const formModel = new FormCloud();
+ const outcomeName = 'Custom Action';
+ const outcome = new FormOutcomeModel( formModel, { id: 'custom1', name: outcomeName });
+
+ formComponent.readOnly = false;
+ formComponent.form = null;
+ expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
+ });
+
+ it('should not execute unknown system outcome', () => {
+ const formModel = new FormCloud();
+ const outcome = new FormOutcomeModel( formModel, { id: 'unknown', name: 'Unknown', isSystem: true });
+
+ formComponent.form = formModel;
+ expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
+ });
+
+ it('should require custom action name to complete form', () => {
+ const formModel = new FormCloud();
+ let outcome = new FormOutcomeModel( formModel, { id: 'custom' });
+
+ formComponent.form = formModel;
+ expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
+
+ outcome = new FormOutcomeModel( formModel, { id: 'custom', name: 'Custom' });
+ spyOn(formComponent, 'completeTaskForm').and.stub();
+ expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
+ });
+
+ it('should fetch and parse form by task id', (done) => {
+ const appName = 'test-app';
+ const taskId = '456';
+
+ spyOn(formService, 'getTask').and.returnValue(of({}));
+ spyOn(formService, 'getTaskVariables').and.returnValue(of({}));
+ spyOn(formService, 'getTaskForm').and.returnValue(of({formRepresentation: {taskId: taskId, formDefinition: {selectedOutcome: 'custom-outcome'}}}));
+
+ formComponent.formLoaded.subscribe(() => {
+ expect(formService.getTaskForm).toHaveBeenCalledWith(appName, taskId);
+ expect(formComponent.form).toBeDefined();
+ expect(formComponent.form.taskId).toBe(taskId);
+ done();
+ });
+
+ formComponent.appName = appName;
+ formComponent.taskId = taskId;
+ formComponent.loadForm();
+ });
+
+ it('should handle error when getting form by task id', (done) => {
+ const error = 'Some error';
+
+ spyOn(formService, 'getTask').and.returnValue(of({}));
+ spyOn(formService, 'getTaskVariables').and.returnValue(of({}));
+ spyOn(formComponent, 'handleError').and.stub();
+ spyOn(formService, 'getTaskForm').and.callFake(() => {
+ return throwError(error);
+ });
+
+ formComponent.getFormByTaskId('test-app', '123').then((_) => {
+ expect(formComponent.handleError).toHaveBeenCalledWith(error);
+ done();
+ });
+ });
+
+ it('should fetch and parse form definition by id', (done) => {
+ spyOn(formService, 'getForm').and.callFake((currentAppName, currentFormId) => {
+ return new Observable((observer) => {
+ observer.next({ formRepresentation: {id: currentFormId, formDefinition: {}}});
+ observer.complete();
+ });
+ });
+
+ const appName = 'test-app';
+ const formId = '456';
+ formComponent.formLoaded.subscribe(() => {
+ expect(formComponent.form).toBeDefined();
+ expect(formComponent.form.id).toBe(formId);
+ done();
+ });
+
+ formComponent.appName = appName;
+ formComponent.formId = formId;
+ formComponent.loadForm();
+ });
+
+ it('should handle error when getting form by definition id', () => {
+ const error = 'Some error';
+
+ spyOn(formComponent, 'handleError').and.stub();
+ spyOn(formService, 'getForm').and.callFake(() => throwError(error));
+
+ formComponent.getFormById('test-app', '123');
+ expect(formComponent.handleError).toHaveBeenCalledWith(error);
+ });
+
+ it('should save task form and raise corresponding event', () => {
+ spyOn(formService, 'saveTaskForm').and.callFake(() => {
+ return new Observable((observer) => {
+ observer.next();
+ observer.complete();
+ });
+ });
+
+ let saved = false;
+ let savedForm = null;
+ formComponent.formSaved.subscribe((form) => {
+ saved = true;
+ savedForm = form;
+ });
+
+ const taskId = '123-223';
+ const appName = 'test-app';
+
+ const formModel = new FormCloud({
+ formRepresentation: {
+ id: '23',
+ taskId: taskId,
+ formDefinition: {
+ fields: [
+ { id: 'field1' },
+ { id: 'field2' }
+ ]
+ }
+ }
+ });
+ formComponent.form = formModel;
+ formComponent.taskId = taskId;
+ formComponent.appName = appName;
+
+ formComponent.saveTaskForm();
+
+ expect(formService.saveTaskForm).toHaveBeenCalledWith(appName, formModel.taskId, formModel.id, formModel.values);
+ expect(saved).toBeTruthy();
+ expect(savedForm).toEqual(formModel);
+ });
+
+ it('should handle error during form save', () => {
+ const error = 'Error';
+ spyOn(formService, 'saveTaskForm').and.callFake(() => throwError(error));
+ spyOn(formComponent, 'handleError').and.stub();
+
+ const taskId = '123-223';
+ const appName = 'test-app';
+ const formModel = new FormCloud({
+ formRepresentation: {
+ id: '23',
+ taskId: taskId,
+ formDefinition: {
+ fields: [
+ { id: 'field1' },
+ { id: 'field2' }
+ ]
+ }
+ }
+ });
+ formComponent.form = formModel;
+ formComponent.taskId = taskId;
+ formComponent.appName = appName;
+
+ formComponent.saveTaskForm();
+
+ expect(formComponent.handleError).toHaveBeenCalledWith(error);
+ });
+
+ it('should require form with appName and taskId to save', () => {
+ spyOn(formService, 'saveTaskForm').and.stub();
+
+ formComponent.form = null;
+ formComponent.saveTaskForm();
+
+ formComponent.form = new FormCloud();
+
+ formComponent.appName = 'test-app';
+ formComponent.saveTaskForm();
+
+ formComponent.appName = null;
+ formComponent.taskId = '123';
+ formComponent.saveTaskForm();
+
+ expect(formService.saveTaskForm).not.toHaveBeenCalled();
+ });
+
+ it('should require form with appName and taskId to complete', () => {
+ spyOn(formService, 'completeTaskForm').and.stub();
+
+ formComponent.form = null;
+ formComponent.completeTaskForm('save');
+
+ formComponent.form = new FormCloud();
+ formComponent.appName = 'test-app';
+ formComponent.completeTaskForm('complete');
+
+ formComponent.appName = null;
+ formComponent.taskId = '123';
+ formComponent.completeTaskForm('complete');
+
+ expect(formService.completeTaskForm).not.toHaveBeenCalled();
+ });
+
+ it('should complete form and raise corresponding event', () => {
+ spyOn(formService, 'completeTaskForm').and.callFake(() => {
+ return new Observable((observer) => {
+ observer.next();
+ observer.complete();
+ });
+ });
+
+ const outcome = 'complete';
+ let completed = false;
+ formComponent.formCompleted.subscribe(() => completed = true);
+
+ const taskId = '123-223';
+ const appName = 'test-app';
+ const formModel = new FormCloud({
+ formRepresentation: {
+ id: '23',
+ taskId: taskId,
+ formDefinition: {
+ fields: [
+ { id: 'field1' },
+ { id: 'field2' }
+ ]
+ }
+ }
+ });
+
+ formComponent.form = formModel;
+ formComponent.taskId = taskId;
+ formComponent.appName = appName;
+ formComponent.completeTaskForm(outcome);
+
+ expect(formService.completeTaskForm).toHaveBeenCalledWith(appName, formModel.taskId, formModel.id, formModel.values, outcome);
+ expect(completed).toBeTruthy();
+ });
+
+ it('should require json to parse form', () => {
+ expect(formComponent.parseForm(null)).toBeNull();
+ });
+
+ it('should parse form from json', () => {
+ const form = formComponent.parseForm({
+ formRepresentation: {
+ id: '1',
+ formDefinition: {
+ fields: [
+ { id: 'field1', type: FormFieldTypes.CONTAINER }
+ ]
+ }
+ }
+ });
+
+ expect(form).toBeDefined();
+ expect(form.id).toBe('1');
+ expect(form.fields.length).toBe(1);
+ expect(form.fields[0].id).toBe('field1');
+ });
+
+ it('should provide outcomes for form definition', () => {
+ spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough();
+
+ const form = formComponent.parseForm({ formRepresentation: { id: 1, formDefinition: {}}});
+ expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form);
+ });
+
+ it('should prevent default outcome execution', () => {
+
+ const outcome = new FormOutcomeModel( new FormCloud(), {
+ id: FormCloudComponent.CUSTOM_OUTCOME_ID,
+ name: 'Custom'
+ });
+
+ formComponent.form = new FormCloud();
+ formComponent.executeOutcome.subscribe((event: FormOutcomeEvent) => {
+ expect(event.outcome).toBe(outcome);
+ event.preventDefault();
+ expect(event.defaultPrevented).toBeTruthy();
+ });
+
+ const result = formComponent.onOutcomeClicked(outcome);
+ expect(result).toBeFalsy();
+ });
+
+ it('should not prevent default outcome execution', () => {
+ const outcome = new FormOutcomeModel( new FormCloud(), {
+ id: FormCloudComponent.CUSTOM_OUTCOME_ID,
+ name: 'Custom'
+ });
+
+ formComponent.form = new FormCloud();
+ formComponent.executeOutcome.subscribe((event: FormOutcomeEvent) => {
+ expect(event.outcome).toBe(outcome);
+ expect(event.defaultPrevented).toBeFalsy();
+ });
+
+ spyOn(formComponent, 'completeTaskForm').and.callThrough();
+
+ const result = formComponent.onOutcomeClicked(outcome);
+ expect(result).toBeTruthy();
+
+ expect(formComponent.completeTaskForm).toHaveBeenCalledWith(outcome.name);
+ });
+
+ it('should check visibility only if field with form provided', () => {
+
+ formComponent.checkVisibility(null);
+ expect(visibilityService.refreshVisibility).not.toHaveBeenCalled();
+
+ let field = new FormFieldModel(null);
+ formComponent.checkVisibility(field);
+ expect(visibilityService.refreshVisibility).not.toHaveBeenCalled();
+
+ field = new FormFieldModel( new FormCloud());
+ formComponent.checkVisibility(field);
+ expect(visibilityService.refreshVisibility).toHaveBeenCalledWith(field.form);
+ });
+
+ it('should disable outcome buttons for readonly form', () => {
+ const formModel = new FormCloud();
+ formModel.readOnly = true;
+ formComponent.form = formModel;
+
+ const outcome = new FormOutcomeModel( new FormCloud(), {
+ id: FormCloudComponent.CUSTOM_OUTCOME_ID,
+ name: 'Custom'
+ });
+
+ expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
+ });
+
+ it('should require outcome to eval button state', () => {
+ formComponent.form = new FormCloud();
+ expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy();
+ });
+
+ it('should disable complete outcome button when disableCompleteButton is true', () => {
+ const formModel = new FormCloud();
+ formComponent.form = formModel;
+ formComponent.disableCompleteButton = true;
+
+ expect(formModel.isValid).toBeTruthy();
+ const completeOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.COMPLETE_ACTION);
+
+ expect(formComponent.isOutcomeButtonEnabled(completeOutcome)).toBeFalsy();
+ });
+
+ it('should disable start process outcome button when disableStartProcessButton is true', () => {
+ const formModel = new FormCloud();
+ formComponent.form = formModel;
+ formComponent.disableStartProcessButton = true;
+
+ expect(formModel.isValid).toBeTruthy();
+ const startProcessOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.START_PROCESS_ACTION);
+
+ expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeFalsy();
+ });
+
+ it('should raise [executeOutcome] event for formService', (done) => {
+ formComponent.executeOutcome.subscribe(() => {
+ done();
+ });
+
+ const outcome = new FormOutcomeModel( new FormCloud(), {
+ id: FormCloudComponent.CUSTOM_OUTCOME_ID,
+ name: 'Custom'
+ });
+
+ formComponent.form = new FormCloud();
+ formComponent.onOutcomeClicked(outcome);
+ });
+
+ it('should refresh form values when data is changed', () => {
+ formComponent.form = new FormCloud(JSON.parse(JSON.stringify(cloudFormMock)));
+ let formFields = formComponent.form.getFormFields();
+
+ let labelField = formFields.find((field) => field.id === 'text1');
+ let radioField = formFields.find((field) => field.id === 'number1');
+ expect(labelField.value).toBeNull();
+ expect(radioField.value).toBeUndefined();
+
+ const formValues: any[] = [{name: 'text1', value: 'test'}, {name: 'number1', value: 23}];
+
+ const change = new SimpleChange(null, formValues, false);
+ formComponent.data = formValues;
+ formComponent.ngOnChanges({ 'data': change });
+
+ formFields = formComponent.form.getFormFields();
+ labelField = formFields.find((field) => field.id === 'text1');
+ radioField = formFields.find((field) => field.id === 'number1');
+ expect(labelField.value).toBe('test');
+ expect(radioField.value).toBe(23);
+ });
+
+ it('should refresh radio buttons value when id is given to data', () => {
+ formComponent.form = new FormCloud(JSON.parse(JSON.stringify(cloudFormMock)));
+ let formFields = formComponent.form.getFormFields();
+ let radioFieldById = formFields.find((field) => field.id === 'radiobuttons1');
+
+ const formValues: any[] = [{name: 'radiobuttons1', value: 'option_2'}];
+ const change = new SimpleChange(null, formValues, false);
+ formComponent.data = formValues;
+ formComponent.ngOnChanges({ 'data': change });
+
+ formFields = formComponent.form.getFormFields();
+ radioFieldById = formFields.find((field) => field.id === 'radiobuttons1');
+ expect(radioFieldById.value).toBe('option_2');
+ });
+});
diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts
new file mode 100644
index 0000000000..368a522ae4
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts
@@ -0,0 +1,296 @@
+/*!
+ * @license
+ * Copyright 2019 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, EventEmitter, Input, OnChanges,
+ Output, SimpleChanges
+} from '@angular/core';
+import { Observable, of, forkJoin } from 'rxjs';
+import { switchMap } from 'rxjs/operators';
+import { Subscription } from 'rxjs';
+import { FormBaseComponent, FormFieldModel, FormOutcomeEvent, FormOutcomeModel, WidgetVisibilityService } from '@alfresco/adf-core';
+import { FormCloudService } from '../services/form-cloud.service';
+import { FormCloud } from '../models/form-cloud.model';
+import { TaskVariableCloud } from '../models/task-variable-cloud.model';
+
+@Component({
+ selector: 'adf-cloud-form',
+ templateUrl: './form-cloud.component.html'
+})
+export class FormCloudComponent extends FormBaseComponent implements OnChanges {
+
+ /** App id to fetch corresponding form and values. */
+ @Input()
+ appName: string;
+
+ /** Task id to fetch corresponding form and values. */
+ @Input()
+ formId: string;
+
+ /** Underlying form model instance. */
+ @Input()
+ form: FormCloud;
+
+ /** Task id to fetch corresponding form and values. */
+ @Input()
+ taskId: string;
+
+ /** Custom form values map to be used with the rendered form. */
+ @Input()
+ data: TaskVariableCloud[];
+
+ /** Emitted when the form is submitted with the `Save` or custom outcomes. */
+ @Output()
+ formSaved: EventEmitter = new EventEmitter();
+
+ /** Emitted when the form is submitted with the `Complete` outcome. */
+ @Output()
+ formCompleted: EventEmitter = new EventEmitter();
+
+ /** Emitted when the form is loaded or reloaded. */
+ @Output()
+ formLoaded: EventEmitter = new EventEmitter();
+
+ /** Emitted when form values are refreshed due to a data property change. */
+ @Output()
+ formDataRefreshed: EventEmitter = new EventEmitter();
+
+ protected subscriptions: Subscription[] = [];
+ nodeId: string;
+
+ constructor(protected formService: FormCloudService,
+ protected visibilityService: WidgetVisibilityService) {
+ super();
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ const appName = changes['appName'];
+ if (appName && appName.currentValue) {
+ if (this.taskId) {
+ this.getFormDefinitionWithFolderTask(this.appName, this.taskId);
+ } else if (this.formId) {
+ this.getFormById(appName.currentValue, this.formId);
+ }
+ return;
+ }
+
+ const formId = changes['formId'];
+ if (formId && formId.currentValue && this.appName) {
+ this.getFormById(this.appName, formId.currentValue);
+ return;
+ }
+
+ const taskId = changes['taskId'];
+ if (taskId && taskId.currentValue && this.appName) {
+ this.getFormByTaskId(this.appName, taskId.currentValue);
+ return;
+ }
+
+ const data = changes['data'];
+ if (data && data.currentValue) {
+ this.refreshFormData();
+ return;
+ }
+ }
+
+ /**
+ * Invoked when user clicks form refresh button.
+ */
+ onRefreshClicked() {
+ this.loadForm();
+ }
+
+ loadForm() {
+ if (this.appName && this.taskId) {
+ this.getFormByTaskId(this.appName, this.taskId);
+ } else if (this.appName && this.formId) {
+ this.getFormById(this.appName, this.formId);
+ }
+
+ }
+
+ findProcessVariablesByTaskId(appName: string, taskId: string): Observable {
+ return this.formService.getTask(appName, taskId).pipe(
+ switchMap((task: any) => {
+ if (this.isAProcessTask(task)) {
+ return this.formService.getTaskVariables(appName, taskId);
+ } else {
+ return of({});
+ }
+ })
+ );
+ }
+
+ isAProcessTask(taskRepresentation) {
+ return taskRepresentation.processDefinitionId && taskRepresentation.processDefinitionDeploymentId !== 'null';
+ }
+
+ getFormByTaskId(appName, taskId: string): Promise {
+ return new Promise((resolve, reject) => {
+ forkJoin(this.formService.getTaskForm(appName, taskId),
+ this.formService.getTaskVariables(appName, taskId))
+ .subscribe(
+ (data) => {
+ this.data = data[1];
+ const parsedForm = this.parseForm(data[0]);
+ this.visibilityService.refreshVisibility( parsedForm);
+ parsedForm.validateForm();
+ this.form = parsedForm;
+ this.form.nodeId = this.nodeId;
+ this.onFormLoaded(this.form);
+ resolve(this.form);
+ },
+ (error) => {
+ this.handleError(error);
+ // reject(error);
+ resolve(null);
+ }
+ );
+ });
+ }
+
+ async getFormDefinitionWithFolderTask(appName: string, taskId: string) {
+ await this.getFolderTask(appName, taskId);
+ await this.getFormByTaskId(appName, taskId);
+ }
+
+ async getFolderTask(appName: string, taskId: string) {
+ this.nodeId = await this.formService.getProcessStorageFolderTask(appName, taskId).toPromise();
+ }
+
+ getFormById(appName: string, formId: string) {
+ this.formService
+ .getForm(appName, formId)
+ .subscribe(
+ (form) => {
+ const parsedForm = this.parseForm(form);
+ this.visibilityService.refreshVisibility( parsedForm);
+ parsedForm.validateForm();
+ this.form = parsedForm;
+ this.form.nodeId = this.nodeId;
+ this.onFormLoaded(this.form);
+ },
+ (error) => {
+ this.handleError(error);
+ }
+ );
+ }
+
+ saveTaskForm() {
+ if (this.form && this.appName && this.taskId) {
+ this.formService
+ .saveTaskForm(this.appName, this.taskId, this.form.id, this.form.values)
+ .subscribe(
+ () => {
+ this.onTaskSaved(this.form);
+ },
+ (error) => this.onTaskSavedError(this.form, error)
+ );
+ }
+ }
+
+ completeTaskForm(outcome?: string) {
+ if (this.form && this.appName && this.taskId) {
+ this.formService
+ .completeTaskForm(this.appName, this.taskId, this.form.id, this.form.values, outcome)
+ .subscribe(
+ () => {
+ this.onTaskCompleted(this.form);
+ },
+ (error) => this.onTaskCompletedError(this.form, error)
+ );
+ }
+ }
+
+ parseForm(json: any): FormCloud {
+ if (json) {
+ const form = new FormCloud(json, this.data, this.readOnly, this.formService);
+ if (!json.formRepresentation.formDefinition || !json.formRepresentation.formDefinition.fields) {
+ form.outcomes = this.getFormDefinitionOutcomes(form);
+ }
+ if (this.fieldValidators && this.fieldValidators.length > 0) {
+ form.fieldValidators = this.fieldValidators;
+ }
+ return form;
+ }
+ return null;
+ }
+
+ /**
+ * Get custom set of outcomes for a Form Definition.
+ * @param form Form definition model.
+ */
+ getFormDefinitionOutcomes(form: FormCloud): FormOutcomeModel[] {
+ return [
+ new FormOutcomeModel( form, { id: '$save', name: FormOutcomeModel.SAVE_ACTION, isSystem: true })
+ ];
+ }
+
+ checkVisibility(field: FormFieldModel) {
+ if (field && field.form) {
+ this.visibilityService.refreshVisibility(field.form);
+ }
+ }
+
+ private refreshFormData() {
+ this.form = this.parseForm(this.form.json);
+ this.onFormLoaded(this.form);
+ this.onFormDataRefreshed(this.form);
+ }
+
+ protected onFormLoaded(form: FormCloud) {
+ this.formLoaded.emit(form);
+ }
+
+ protected onFormDataRefreshed(form: FormCloud) {
+ this.formDataRefreshed.emit(form);
+ }
+
+ protected onTaskSaved(form: FormCloud) {
+ this.formSaved.emit(form);
+ }
+
+ protected onTaskSavedError(form: FormCloud, error: any) {
+ this.handleError(error);
+ }
+
+ protected onTaskCompleted(form: FormCloud) {
+ this.formCompleted.emit(form);
+ }
+
+ protected onTaskCompletedError(form: FormCloud, error: any) {
+ this.handleError(error);
+ }
+
+ protected onExecuteOutcome(outcome: FormOutcomeModel): boolean {
+ const args = new FormOutcomeEvent(outcome);
+
+ if (args.defaultPrevented) {
+ return false;
+ }
+
+ this.executeOutcome.emit(args);
+ if (args.defaultPrevented) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected storeFormAsMetadata() {
+ }
+}
diff --git a/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.html
new file mode 100644
index 0000000000..1e8b6a207f
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.html
@@ -0,0 +1,40 @@
+
diff --git a/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.scss b/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.ts
new file mode 100644
index 0000000000..7a4f8b2beb
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/components/upload-cloud.widget.ts
@@ -0,0 +1,135 @@
+/*!
+ * @license
+ * Copyright 2019 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.
+ */
+
+/* tslint:disable:component-selector */
+
+import { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
+import { Observable, from } from 'rxjs';
+import { mergeMap, map } from 'rxjs/operators';
+import { WidgetComponent, baseHost, LogService, FormService, ThumbnailService } from '@alfresco/adf-core';
+import { FormCloudService } from '../services/form-cloud.service';
+
+@Component({
+ selector: 'upload-cloud-widget',
+ templateUrl: './upload-cloud.widget.html',
+ styleUrls: ['./upload-cloud.widget.scss'],
+ host: baseHost,
+ encapsulation: ViewEncapsulation.None
+})
+export class UploadCloudWidgetComponent extends WidgetComponent implements OnInit {
+
+ hasFile: boolean;
+ displayText: string;
+ multipleOption: string = '';
+ mimeTypeIcon: string;
+
+ @ViewChild('uploadFiles')
+ fileInput: ElementRef;
+
+ constructor(public formService: FormService,
+ private thumbnailService: ThumbnailService,
+ private formCloudService: FormCloudService,
+ private logService: LogService) {
+ super(formService);
+ }
+
+ ngOnInit() {
+ if (this.field &&
+ this.field.value &&
+ this.field.value.length > 0) {
+ this.hasFile = true;
+ }
+ this.getMultipleFileParam();
+ }
+
+ removeFile(file: any) {
+ if (this.field) {
+ this.removeElementFromList(file);
+ }
+ }
+
+ onFileChanged(event: any) {
+ const files = event.target.files;
+ let filesSaved = [];
+
+ if (this.field.json.value) {
+ filesSaved = [...this.field.json.value];
+ }
+
+ if (files && files.length > 0) {
+ from(files)
+ .pipe(mergeMap((file) => this.uploadRawContent(file)))
+ .subscribe(
+ (res) => filesSaved.push(res),
+ (error) => this.logService.error(`Error uploading file. See console output for more details. ${error}` ),
+ () => {
+ this.field.form.values[this.field.id] = filesSaved;
+ this.hasFile = true;
+ }
+ );
+ }
+ }
+
+ getIcon(mimeType) {
+ return this.thumbnailService.getMimeTypeIcon(mimeType);
+ }
+
+ private uploadRawContent(file): Observable {
+ return this.formCloudService.createTemporaryRawRelatedContent(file, this.field.form.nodeId)
+ .pipe(
+ map((response: any) => {
+ this.logService.info(response);
+ return { nodeId : response.id};
+ })
+ );
+ }
+
+ getMultipleFileParam() {
+ if (this.field &&
+ this.field.params &&
+ this.field.params.multiple) {
+ this.multipleOption = this.field.params.multiple ? 'multiple' : '';
+ }
+ }
+
+ private removeElementFromList(file) {
+ const index = this.field.value.indexOf(file);
+
+ // remove from content too
+
+ if (index !== -1) {
+ this.field.value.splice(index, 1);
+ this.field.json.value = this.field.value;
+ this.field.updateForm();
+ }
+
+ this.hasFile = this.field.value.length > 0;
+
+ this.resetFormValueWithNoFiles();
+ }
+
+ private resetFormValueWithNoFiles() {
+ if (this.field.value.length === 0) {
+ this.field.value = [];
+ this.field.json.value = [];
+ }
+ }
+
+ fileClicked(contentLinkModel: any): void {
+
+ }
+}
diff --git a/lib/process-services-cloud/src/lib/form/form-cloud.module.ts b/lib/process-services-cloud/src/lib/form/form-cloud.module.ts
new file mode 100644
index 0000000000..74f71a73c0
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/form-cloud.module.ts
@@ -0,0 +1,47 @@
+/*!
+ * @license
+ * Copyright 2019 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 { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { TemplateModule, FormBaseModule, PipeModule, CoreModule } from '@alfresco/adf-core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { FormCloudComponent } from './components/form-cloud.component';
+import { UploadCloudWidgetComponent } from './components/upload-cloud.widget';
+import { MaterialModule } from '../material.module';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ PipeModule,
+ TemplateModule,
+ FlexLayoutModule,
+ MaterialModule,
+ FormsModule,
+ ReactiveFormsModule,
+ FormBaseModule,
+ CoreModule
+ ],
+ declarations: [FormCloudComponent, UploadCloudWidgetComponent],
+ entryComponents: [
+ UploadCloudWidgetComponent
+ ],
+ exports: [
+ FormCloudComponent, UploadCloudWidgetComponent
+ ]
+})
+export class FormCloudModule { }
diff --git a/lib/process-services-cloud/src/lib/form/mocks/cloud-form.mock.ts b/lib/process-services-cloud/src/lib/form/mocks/cloud-form.mock.ts
new file mode 100644
index 0000000000..988c7954f4
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/mocks/cloud-form.mock.ts
@@ -0,0 +1,686 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const cloudFormMock = {
+ 'formRepresentation': {
+ 'id': 'form-b661635a-dc3e-4557-914a-3498ed47189c',
+ 'name': 'form-with-all-fields',
+ 'description': '',
+ 'version': 0,
+ 'formDefinition': {
+ 'tabs': [],
+ 'fields': [
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '26b10e64-0403-4686-a75b-0d45279ce3a8',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'text1',
+ 'name': 'Text1',
+ 'type': 'text',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'minLength': 0,
+ 'maxLength': 0,
+ 'minValue': null,
+ 'maxValue': null,
+ 'regexPattern': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'text2',
+ 'name': 'Text2',
+ 'type': 'text',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'minLength': 0,
+ 'maxLength': 0,
+ 'minValue': null,
+ 'maxValue': null,
+ 'regexPattern': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '69c1390a-8d8d-423c-8efb-8e43401efa42',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'multilinetext1',
+ 'name': 'Multiline text1',
+ 'type': 'multi-line-text',
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'minLength': 0,
+ 'maxLength': 0,
+ 'regexPattern': null,
+ 'required': false,
+ 'readOnly': true,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'multilinetext2',
+ 'name': 'Multiline text2',
+ 'type': 'multi-line-text',
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'minLength': 0,
+ 'maxLength': 0,
+ 'regexPattern': null,
+ 'required': false,
+ 'readOnly': true,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': 'df046463-2d65-4388-9ee1-0e1517985215',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'number1',
+ 'overrideId': false,
+ 'name': 'Number1',
+ 'type': 'integer',
+ 'colspan': 1,
+ 'placeholder': null,
+ 'readOnly': true,
+ 'minValue': null,
+ 'maxValue': null,
+ 'required': false,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'number2',
+ 'overrideId': false,
+ 'name': 'Number2',
+ 'type': 'integer',
+ 'colspan': 1,
+ 'placeholder': null,
+ 'readOnly': true,
+ 'minValue': null,
+ 'maxValue': null,
+ 'required': false,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '9672cc7b-1959-49c9-96be-3816e57bdfc1',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'checkbox1',
+ 'name': 'Checkbox1',
+ 'type': 'boolean',
+ 'required': false,
+ 'readOnly': true,
+ 'colspan': 1,
+ 'overrideId': false,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'checkbox2',
+ 'name': 'Checkbox2',
+ 'type': 'boolean',
+ 'required': false,
+ 'readOnly': true,
+ 'colspan': 1,
+ 'overrideId': false,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '054d193e-a899-4494-9a3e-b489315b7d57',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'dropdown1',
+ 'name': 'Dropdown1',
+ 'type': 'dropdown',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'optionType': 'manual',
+ 'options': [],
+ 'endpoint': null,
+ 'requestHeaders': null,
+ 'restUrl': null,
+ 'restResponsePath': null,
+ 'restIdProperty': null,
+ 'restLabelProperty': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'dropdown2',
+ 'name': 'Dropdown2',
+ 'type': 'dropdown',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'optionType': 'manual',
+ 'options': [],
+ 'endpoint': null,
+ 'requestHeaders': null,
+ 'restUrl': null,
+ 'restResponsePath': null,
+ 'restIdProperty': null,
+ 'restLabelProperty': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '1f8f0b66-e022-4667-91b4-bbbf2ddc36fb',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'amount1',
+ 'name': 'Amount1',
+ 'type': 'amount',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': '123',
+ 'minValue': null,
+ 'maxValue': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ },
+ 'enableFractions': false,
+ 'currency': '$'
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'amount2',
+ 'name': 'Amount2',
+ 'type': 'amount',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': '123',
+ 'minValue': null,
+ 'maxValue': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ },
+ 'enableFractions': false,
+ 'currency': '$'
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '541a368b-67ee-4a7c-ae7e-232c050b9e24',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'date1',
+ 'name': 'Date1',
+ 'type': 'date',
+ 'overrideId': false,
+ 'required': false,
+ 'readOnly': true,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'minValue': null,
+ 'maxValue': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ },
+ 'dateDisplayFormat': 'D-M-YYYY'
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'date2',
+ 'name': 'Date2',
+ 'type': 'date',
+ 'overrideId': false,
+ 'required': false,
+ 'readOnly': true,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'minValue': null,
+ 'maxValue': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ },
+ 'dateDisplayFormat': 'D-M-YYYY'
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': 'e79cb7e2-3dc1-4c79-8158-28662c28a9f3',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'radiobuttons1',
+ 'name': 'Radio buttons1',
+ 'type': 'radio-buttons',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'optionType': 'manual',
+ 'options': [
+ {
+ 'id': 'option_1',
+ 'name': 'Option 1'
+ },
+ {
+ 'id': 'option_2',
+ 'name': 'Option 2'
+ }
+ ],
+ 'endpoint': null,
+ 'requestHeaders': null,
+ 'restUrl': null,
+ 'restResponsePath': null,
+ 'restIdProperty': null,
+ 'restLabelProperty': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'radiobuttons2',
+ 'name': 'Radio buttons2',
+ 'type': 'radio-buttons',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'optionType': 'manual',
+ 'options': [
+ {
+ 'id': 'option_1',
+ 'name': 'Option 1'
+ },
+ {
+ 'id': 'option_2',
+ 'name': 'Option 2'
+ }
+ ],
+ 'endpoint': null,
+ 'requestHeaders': null,
+ 'restUrl': null,
+ 'restResponsePath': null,
+ 'restIdProperty': null,
+ 'restLabelProperty': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '7c01ed35-be86-4be7-9c28-ed640a5a2ae1',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'AttachFileFieldRepresentation',
+ 'id': 'attachfile1',
+ 'name': 'Attach file1',
+ 'type': 'upload',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2,
+ 'fileSource': {
+ 'serviceId': 'all-file-sources',
+ 'name': 'All file sources'
+ },
+ 'multiple': false,
+ 'link': false
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'AttachFileFieldRepresentation',
+ 'id': 'attachfile2',
+ 'name': 'Attach file2',
+ 'type': 'upload',
+ 'value': null,
+ 'required': false,
+ 'readOnly': true,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'placeholder': null,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2,
+ 'fileSource': {
+ 'serviceId': 'all-file-sources',
+ 'name': 'All file sources'
+ },
+ 'multiple': false,
+ 'link': false
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '07b13b96-d469-4a1e-8a9a-9bb957c68869',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'displayvalue1',
+ 'name': 'Display value1',
+ 'type': 'readonly',
+ 'value': 'No field selected',
+ 'readOnly': true,
+ 'required': false,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2,
+ 'field': {
+ 'id': 'displayvalue',
+ 'name': 'Display value',
+ 'type': 'text'
+ }
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'displayvalue2',
+ 'name': 'Display value2',
+ 'type': 'readonly',
+ 'value': 'No field selected',
+ 'readOnly': true,
+ 'required': false,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2,
+ 'field': {
+ 'id': 'displayvalue',
+ 'name': 'Display value',
+ 'type': 'text'
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'fieldType': 'ContainerRepresentation',
+ 'id': '1576ef25-c842-494c-ab84-265a1e3bf68d',
+ 'name': 'Label',
+ 'type': 'container',
+ 'tab': null,
+ 'numberOfColumns': 2,
+ 'fields': {
+ '1': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'displaytext1',
+ 'name': 'Display text1',
+ 'type': 'readonly-text',
+ 'value': 'Display text as part of the form',
+ 'readOnly': true,
+ 'required': false,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ],
+ '2': [
+ {
+ 'fieldType': 'FormFieldRepresentation',
+ 'id': 'displaytext2',
+ 'name': 'Display text2',
+ 'type': 'readonly-text',
+ 'value': 'Display text as part of the form',
+ 'readOnly': true,
+ 'required': false,
+ 'overrideId': false,
+ 'colspan': 1,
+ 'visibilityCondition': null,
+ 'params': {
+ 'existingColspan': 1,
+ 'maxColspan': 2
+ }
+ }
+ ]
+ }
+ }
+ ],
+ 'outcomes': [],
+ 'javascriptEvents': [],
+ 'className': '',
+ 'style': '',
+ 'customFieldTemplates': {},
+ 'metadata': {},
+ 'variables': [
+ {
+ 'name': 'FormVarStr',
+ 'type': 'string',
+ 'value': ''
+ },
+ {
+ 'name': 'FormVarInt',
+ 'type': 'integer',
+ 'value': ''
+ },
+ {
+ 'name': 'FormVarBool',
+ 'type': 'boolean',
+ 'value': ''
+ },
+ {
+ 'name': 'FormVarDate',
+ 'type': 'date',
+ 'value': ''
+ },
+ {
+ 'name': 'NewVar',
+ 'type': 'string',
+ 'value': ''
+ }
+ ],
+ 'customFieldsValueInfo': {},
+ 'gridsterForm': false
+ }
+ },
+ 'processScopeIdentifiers': []
+};
diff --git a/lib/process-services-cloud/src/lib/form/models/form-cloud.model.spec.ts b/lib/process-services-cloud/src/lib/form/models/form-cloud.model.spec.ts
new file mode 100644
index 0000000000..7a9b1a5fdf
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/models/form-cloud.model.spec.ts
@@ -0,0 +1,233 @@
+/*!
+ * @license
+ * Copyright 2019 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 { FormCloudService } from '../services/form-cloud.service';
+import { FormCloud } from './form-cloud.model';
+import { TabModel, FormFieldModel, ContainerModel, FormOutcomeModel, FormFieldTypes } from '@alfresco/adf-core';
+
+describe('FormCloud', () => {
+
+ let formCloudService: FormCloudService;
+
+ beforeEach(() => {
+ formCloudService = new FormCloudService(null, null, null);
+ });
+
+ it('should store original json', () => {
+ const json = {formRepresentation: {formDefinition: {}}};
+ const form = new FormCloud(json);
+ expect(form.json).toBe(json);
+ });
+
+ it('should setup properties with json', () => {
+ const json = {formRepresentation: {
+ id: '',
+ name: '',
+ taskId: '',
+ taskName: ''
+ }};
+ const form = new FormCloud(json);
+
+ Object.keys(json).forEach((key) => {
+ expect(form[key]).toEqual(form[key]);
+ });
+ });
+
+ it('should take form name when task name is missing', () => {
+ const json = {formRepresentation: {
+ id: '',
+ name: '',
+ formDefinition: {}
+ }};
+ const form = new FormCloud(json);
+ expect(form.taskName).toBe(json.formRepresentation.name);
+ });
+
+ it('should set readonly state from params', () => {
+ const form = new FormCloud({}, null, true);
+ expect(form.readOnly).toBeTruthy();
+ });
+
+ it('should check tabs', () => {
+ const form = new FormCloud();
+
+ form.tabs = null;
+ expect(form.hasTabs()).toBeFalsy();
+
+ form.tabs = [];
+ expect(form.hasTabs()).toBeFalsy();
+
+ form.tabs = [new TabModel(null)];
+ expect(form.hasTabs()).toBeTruthy();
+ });
+
+ it('should check fields', () => {
+ const form = new FormCloud();
+
+ form.fields = null;
+ expect(form.hasFields()).toBeFalsy();
+
+ form.fields = [];
+ expect(form.hasFields()).toBeFalsy();
+
+ const field = new FormFieldModel( form);
+ form.fields = [new ContainerModel(field)];
+ expect(form.hasFields()).toBeTruthy();
+ });
+
+ it('should check outcomes', () => {
+ const form = new FormCloud();
+
+ form.outcomes = null;
+ expect(form.hasOutcomes()).toBeFalsy();
+
+ form.outcomes = [];
+ expect(form.hasOutcomes()).toBeFalsy();
+
+ form.outcomes = [new FormOutcomeModel(null)];
+ expect(form.hasOutcomes()).toBeTruthy();
+ });
+
+ it('should parse tabs', () => {
+ const json = {formRepresentation: {formDefinition: {
+ tabs: [
+ { id: 'tab1' },
+ { id: 'tab2' }
+ ]
+ }}};
+
+ const form = new FormCloud(json);
+ expect(form.tabs.length).toBe(2);
+ expect(form.tabs[0].id).toBe('tab1');
+ expect(form.tabs[1].id).toBe('tab2');
+ });
+
+ it('should parse fields', () => {
+ const json = {formRepresentation: {formDefinition: {
+ fields: [
+ {
+ id: 'field1',
+ type: FormFieldTypes.CONTAINER
+ },
+ {
+ id: 'field2',
+ type: FormFieldTypes.CONTAINER
+ }
+ ]
+ }}};
+
+ const form = new FormCloud(json);
+ expect(form.fields.length).toBe(2);
+ expect(form.fields[0].id).toBe('field1');
+ expect(form.fields[1].id).toBe('field2');
+ });
+
+ it('should convert missing fields to empty collection', () => {
+ const json = {formRepresentation: {formDefinition: {
+ fields: null
+ }}};
+
+ const form = new FormCloud(json);
+ expect(form.fields).toBeDefined();
+ expect(form.fields.length).toBe(0);
+ });
+
+ it('should put fields into corresponding tabs', () => {
+ const json = {formRepresentation: {formDefinition: {
+ tabs: [
+ { id: 'tab1' },
+ { id: 'tab2' }
+ ],
+ fields: [
+ { id: 'field1', tab: 'tab1', type: FormFieldTypes.CONTAINER },
+ { id: 'field2', tab: 'tab2', type: FormFieldTypes.CONTAINER },
+ { id: 'field3', tab: 'tab1', type: FormFieldTypes.DYNAMIC_TABLE },
+ { id: 'field4', tab: 'missing-tab', type: FormFieldTypes.DYNAMIC_TABLE }
+ ]
+ }}};
+
+ const form = new FormCloud(json);
+ expect(form.tabs.length).toBe(2);
+ expect(form.fields.length).toBe(4);
+
+ const tab1 = form.tabs[0];
+ expect(tab1.fields.length).toBe(2);
+ expect(tab1.fields[0].id).toBe('field1');
+ expect(tab1.fields[1].id).toBe('field3');
+
+ const tab2 = form.tabs[1];
+ expect(tab2.fields.length).toBe(1);
+ expect(tab2.fields[0].id).toBe('field2');
+ });
+
+ it('should create standard form outcomes', () => {
+ const json = {formRepresentation: {formDefinition: {
+ fields: [
+ { id: 'container1' }
+ ]
+ }}};
+
+ const form = new FormCloud(json);
+ expect(form.outcomes.length).toBe(3);
+
+ expect(form.outcomes[0].id).toBe(FormCloud.SAVE_OUTCOME);
+ expect(form.outcomes[0].isSystem).toBeTruthy();
+
+ expect(form.outcomes[1].id).toBe(FormCloud.COMPLETE_OUTCOME);
+ expect(form.outcomes[1].isSystem).toBeTruthy();
+
+ expect(form.outcomes[2].id).toBe(FormCloud.START_PROCESS_OUTCOME);
+ expect(form.outcomes[2].isSystem).toBeTruthy();
+ });
+
+ it('should create outcomes only when fields available', () => {
+ const json = {formRepresentation: {formDefinition: {
+ fields: null
+ }}};
+ const form = new FormCloud(json);
+ expect(form.outcomes.length).toBe(0);
+ });
+
+ it('should use custom form outcomes', () => {
+ const json = {formRepresentation: {formDefinition: {
+ fields: [
+ { id: 'container1' }
+ ]},
+ outcomes: [
+ { id: 'custom-1', name: 'custom 1' }
+ ]
+ }};
+
+ const form = new FormCloud(json);
+ expect(form.outcomes.length).toBe(2);
+
+ expect(form.outcomes[0].id).toBe(FormCloud.SAVE_OUTCOME);
+ expect(form.outcomes[0].isSystem).toBeTruthy();
+
+ expect(form.outcomes[1].id).toBe('custom-1');
+ expect(form.outcomes[1].isSystem).toBeFalsy();
+ });
+
+ it('should get field by id', () => {
+ const form = new FormCloud({}, null, false, formCloudService);
+ const field: any = { id: 'field1' };
+ spyOn(form, 'getFormFields').and.returnValue([field]);
+
+ const result = form.getFieldById('field1');
+ expect(result).toBe(field);
+ });
+});
diff --git a/lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts b/lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts
new file mode 100644
index 0000000000..f778b25c91
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts
@@ -0,0 +1,247 @@
+/*!
+ * @license
+ * Copyright 2019 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 {
+ TabModel, FormWidgetModel, FormOutcomeModel, FormValues,
+ FormWidgetModelCache, FormFieldModel, ContainerModel, FormFieldTypes,
+ ValidateFormFieldEvent, FormFieldValidator, FormFieldTemplates } from '@alfresco/adf-core';
+import { FormCloudService } from '../services/form-cloud.service';
+import { TaskVariableCloud } from './task-variable-cloud.model';
+
+export class FormCloud {
+
+ static SAVE_OUTCOME: string = '$save';
+ static COMPLETE_OUTCOME: string = '$complete';
+ static START_PROCESS_OUTCOME: string = '$startProcess';
+
+ readonly id: string;
+ nodeId: string;
+ readonly name: string;
+ readonly taskId: string;
+ readonly taskName: string;
+ private _isValid: boolean = true;
+
+ get isValid(): boolean {
+ return this._isValid;
+ }
+
+ readonly selectedOutcome: string;
+ readonly json: any;
+
+ readOnly: boolean;
+ processDefinitionId: any;
+ className: string;
+ values: FormValues = {};
+
+ tabs: TabModel[] = [];
+ fields: FormWidgetModel[] = [];
+ outcomes: FormOutcomeModel[] = [];
+ customFieldTemplates: FormFieldTemplates = {};
+ fieldValidators: FormFieldValidator[] = [];
+
+ constructor(json?: any, formData?: TaskVariableCloud[], readOnly: boolean = false, protected formService?: FormCloudService) {
+ this.readOnly = readOnly;
+
+ if (json && json.formRepresentation && json.formRepresentation.formDefinition) {
+ this.json = json;
+ this.id = json.formRepresentation.id;
+ this.name = json.formRepresentation.name;
+ this.taskId = json.formRepresentation.taskId;
+ this.taskName = json.formRepresentation.taskName || json.formRepresentation.name;
+ this.processDefinitionId = json.formRepresentation.processDefinitionId;
+ this.customFieldTemplates = json.formRepresentation.formDefinition.customFieldTemplates || {};
+ this.selectedOutcome = json.formRepresentation.formDefinition.selectedOutcome || {};
+ this.className = json.formRepresentation.formDefinition.className || '';
+
+ const tabCache: FormWidgetModelCache = {};
+
+ this.tabs = (json.formRepresentation.formDefinition.tabs || []).map((t) => {
+ const model = new TabModel( this, t);
+ tabCache[model.id] = model;
+ return model;
+ });
+
+ this.fields = this.parseRootFields(json);
+
+ if (formData) {
+ this.loadData(formData);
+ }
+
+ for (let i = 0; i < this.fields.length; i++) {
+ const field = this.fields[i];
+ if (field.tab) {
+ const tab = tabCache[field.tab];
+ if (tab) {
+ tab.fields.push(field);
+ }
+ }
+ }
+
+ if (json.formRepresentation.formDefinition.fields) {
+ const saveOutcome = new FormOutcomeModel( this, {
+ id: FormCloud.SAVE_OUTCOME,
+ name: 'SAVE',
+ isSystem: true
+ });
+ const completeOutcome = new FormOutcomeModel( this, {
+ id: FormCloud.COMPLETE_OUTCOME,
+ name: 'COMPLETE',
+ isSystem: true
+ });
+ const startProcessOutcome = new FormOutcomeModel( this, {
+ id: FormCloud.START_PROCESS_OUTCOME,
+ name: 'START PROCESS',
+ isSystem: true
+ });
+
+ const customOutcomes = (json.formRepresentation.outcomes || []).map((obj) => new FormOutcomeModel( this, obj));
+
+ this.outcomes = [saveOutcome].concat(
+ customOutcomes.length > 0 ? customOutcomes : [completeOutcome, startProcessOutcome]
+ );
+ }
+ }
+
+ this.validateForm();
+ }
+
+ hasTabs(): boolean {
+ return this.tabs && this.tabs.length > 0;
+ }
+
+ hasFields(): boolean {
+ return this.fields && this.fields.length > 0;
+ }
+
+ hasOutcomes(): boolean {
+ return this.outcomes && this.outcomes.length > 0;
+ }
+
+ getFieldById(fieldId: string): FormFieldModel {
+ return this.getFormFields().find((field) => field.id === fieldId);
+ }
+
+ onFormFieldChanged(field: FormFieldModel) {
+ this.validateField(field);
+ }
+
+ getFormFields(): FormFieldModel[] {
+ const formFields: FormFieldModel[] = [];
+
+ for (let i = 0; i < this.fields.length; i++) {
+ const field = this.fields[i];
+
+ if (field instanceof ContainerModel) {
+ const container = field;
+ formFields.push(container.field);
+
+ container.field.columns.forEach((column) => {
+ formFields.push(...column.fields);
+ });
+ }
+ }
+
+ return formFields;
+ }
+
+ markAsInvalid() {
+ this._isValid = false;
+ }
+
+ validateForm() {
+ const errorsField: FormFieldModel[] = [];
+
+ const fields = this.getFormFields();
+ for (let i = 0; i < fields.length; i++) {
+ if (!fields[i].validate()) {
+ errorsField.push(fields[i]);
+ }
+ }
+
+ this._isValid = errorsField.length > 0 ? false : true;
+ }
+
+ /**
+ * Validates a specific form field, triggers form validation.
+ *
+ * @param field Form field to validate.
+ * @memberof FormCloud
+ */
+ validateField(field: FormFieldModel) {
+ if (!field) {
+ return;
+ }
+
+ const validateFieldEvent = new ValidateFormFieldEvent( this, field);
+
+ if (!validateFieldEvent.isValid) {
+ this._isValid = false;
+ return;
+ }
+
+ if (validateFieldEvent.defaultPrevented) {
+ return;
+ }
+
+ if (!field.validate()) {
+ this._isValid = false;
+ }
+
+ this.validateForm();
+ }
+
+ // Activiti supports 3 types of root fields: container|group|dynamic-table
+ private parseRootFields(json: any): FormWidgetModel[] {
+ let fields = [];
+
+ if (json.formRepresentation.fields) {
+ fields = json.formRepresentation.fields;
+ } else if (json.formRepresentation.formDefinition && json.formRepresentation.formDefinition.fields) {
+ fields = json.formRepresentation.formDefinition.fields;
+ }
+
+ const formWidgetModel: FormWidgetModel[] = [];
+
+ for (const field of fields) {
+ if (field.type === FormFieldTypes.DISPLAY_VALUE) {
+ // workaround for dynamic table on a completed/readonly form
+ if (field.params) {
+ const originalField = field.params['field'];
+ if (originalField.type === FormFieldTypes.DYNAMIC_TABLE) {
+ formWidgetModel.push(new ContainerModel(new FormFieldModel( this, field)));
+ }
+ }
+ } else {
+ formWidgetModel.push(new ContainerModel(new FormFieldModel( this, field)));
+ }
+ }
+
+ return formWidgetModel;
+ }
+
+ // Loads external data and overrides field values
+ // Typically used when form definition and form data coming from different sources
+ private loadData(formData: TaskVariableCloud[]) {
+ for (const field of this.getFormFields()) {
+ const fieldValue = formData.find((value) => { return value.name === field.id; });
+ if (fieldValue) {
+ field.json.value = fieldValue.value;
+ field.value = field.parseValue(field.json);
+ }
+ }
+ }
+}
diff --git a/lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts b/lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts
new file mode 100644
index 0000000000..05ef43f792
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts
@@ -0,0 +1,25 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export class TaskVariableCloud {
+ name: string;
+ value: any;
+ constructor(obj) {
+ this.name = obj.name || null;
+ this.value = obj.value || null;
+ }
+}
diff --git a/lib/process-services-cloud/src/lib/form/public-api.ts b/lib/process-services-cloud/src/lib/form/public-api.ts
new file mode 100644
index 0000000000..d316dbb6a7
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/public-api.ts
@@ -0,0 +1,22 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './models/form-cloud.model';
+export * from './models/task-variable-cloud.model';
+export * from './components/form-cloud.component';
+export * from './components/upload-cloud.widget';
+export * from './services/form-cloud.service';
diff --git a/lib/process-services-cloud/src/lib/form/services/form-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.spec.ts
new file mode 100644
index 0000000000..2714d0508b
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.spec.ts
@@ -0,0 +1,162 @@
+/*!
+ * @license
+ * Copyright 2019 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 { TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { FormCloudService } from './form-cloud.service';
+import { AlfrescoApiService, CoreModule, setupTestBed, AppConfigService, AppConfigServiceMock } from '@alfresco/adf-core';
+import { of } from 'rxjs';
+
+declare let jasmine: any;
+
+const responseBody = {
+ entry:
+ { id: 'id', name: 'name', formKey: 'form-key' }
+};
+
+const alfrescoApiServiceStub = {
+ getInstance() { },
+ load() { }
+};
+
+const oauth2Auth = jasmine.createSpyObj('oauth2Auth', ['callCustomApi']);
+
+describe('Form Cloud service', () => {
+
+ let service: FormCloudService;
+ let apiService: AlfrescoApiService;
+ const appName = 'app-name';
+ const taskId = 'task-id';
+
+ setupTestBed({
+ imports: [
+ NoopAnimationsModule,
+ CoreModule.forRoot()
+ ],
+ providers: [
+ FormCloudService,
+ { provide: AlfrescoApiService, useValue: alfrescoApiServiceStub },
+ { provide: AppConfigService, useClass: AppConfigServiceMock }
+ ]
+ });
+
+ beforeEach(() => {
+ service = TestBed.get(FormCloudService);
+ apiService = TestBed.get(AlfrescoApiService);
+ spyOn(apiService, 'getInstance').and.returnValue({ oauth2Auth: oauth2Auth });
+ });
+
+ describe('Form tests', () => {
+ it('should fetch and parse form', (done) => {
+ const formId = 'form-id';
+ oauth2Auth.callCustomApi.and.returnValue(Promise.resolve({ formRepresentation: { id: formId, name: 'task-form', taskId: 'task-id' } }));
+
+ service.getForm(appName, formId).subscribe((result) => {
+ expect(result).toBeDefined();
+ expect(result.formRepresentation.id).toBe(formId);
+ expect(result.formRepresentation.name).toBe('task-form');
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[0].endsWith(`${appName}/form/v1/forms/${formId}`)).toBeTruthy();
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[1]).toBe('GET');
+ done();
+ });
+ });
+
+ it('should parse valid form json ', () => {
+ const formId = 'form-id';
+ const json = { formRepresentation: { id: formId, name: 'task-form', taskId: 'task-id', formDefinition: {} } };
+
+ const result = service.parseForm(json);
+ expect(result).toBeDefined();
+ expect(result.id).toBe(formId);
+ expect(result.name).toBe('task-form');
+ });
+ });
+
+ describe('Task tests', () => {
+ it('should fetch and parse task', (done) => {
+ oauth2Auth.callCustomApi.and.returnValue(Promise.resolve(responseBody));
+
+ service.getTask(appName, taskId).subscribe((result) => {
+ expect(result).toBeDefined();
+ expect(result.id).toBe(responseBody.entry.id);
+ expect(result.name).toBe(responseBody.entry.name);
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[0].endsWith(`${appName}/rb/v1/tasks/${taskId}`)).toBeTruthy();
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[1]).toBe('GET');
+ done();
+ });
+
+ });
+
+ it('should fetch task variables', (done) => {
+ oauth2Auth.callCustomApi.and.returnValue(Promise.resolve({ content: { name: 'abc' } }));
+
+ service.getTaskVariables(appName, taskId).subscribe((result: any) => {
+ expect(result).toBeDefined();
+ expect(result.name).toBe('abc');
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[0].endsWith(`${appName}/rb/v1/tasks/${taskId}/variables`)).toBeTruthy();
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[1]).toBe('GET');
+ done();
+ });
+
+ });
+
+ it('should fetch task form', (done) => {
+ spyOn(service, 'getTask').and.returnValue(of(responseBody.entry));
+ spyOn(service, 'getForm').and.returnValue(of({ formRepresentation: { name: 'task-form' } }));
+
+ service.getTaskForm(appName, taskId).subscribe((result) => {
+ expect(result).toBeDefined();
+ expect(result.formRepresentation.name).toBe('task-form');
+ expect(result.formRepresentation.taskId).toBe(responseBody.entry.id);
+ expect(result.formRepresentation.taskName).toBe(responseBody.entry.name);
+ done();
+ });
+
+ });
+
+ it('should save task form', (done) => {
+ oauth2Auth.callCustomApi.and.returnValue(Promise.resolve(responseBody));
+ const formId = 'form-id';
+
+ service.saveTaskForm(appName, taskId, formId, {}).subscribe((result: any) => {
+ expect(result).toBeDefined();
+ expect(result.id).toBe('id');
+ expect(result.name).toBe('name');
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[0].endsWith(`${appName}/form/v1/forms/${formId}/save`)).toBeTruthy();
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[1]).toBe('POST');
+ done();
+ });
+
+ });
+
+ it('should complete task form', (done) => {
+ oauth2Auth.callCustomApi.and.returnValue(Promise.resolve(responseBody));
+ const formId = 'form-id';
+
+ service.completeTaskForm(appName, taskId, formId, {}, '').subscribe((result: any) => {
+ expect(result).toBeDefined();
+ expect(result.id).toBe('id');
+ expect(result.name).toBe('name');
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[0].endsWith(`${appName}/form/v1/forms/${formId}/submit`)).toBeTruthy();
+ expect(oauth2Auth.callCustomApi.calls.mostRecent().args[1]).toBe('POST');
+ done();
+ });
+
+ });
+
+ });
+});
diff --git a/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts
new file mode 100644
index 0000000000..1c4a7ab0ae
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts
@@ -0,0 +1,232 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { AlfrescoApiService, LogService, FormValues, AppConfigService, FormOutcomeModel } from '@alfresco/adf-core';
+import { throwError, Observable, from } from 'rxjs';
+import { catchError, map, switchMap } from 'rxjs/operators';
+import { TaskDetailsCloudModel } from '../../task/start-task/models/task-details-cloud.model';
+import { SaveFormRepresentation, CompleteFormRepresentation } from '@alfresco/js-api';
+import { FormCloud } from '../models/form-cloud.model';
+import { TaskVariableCloud } from '../models/task-variable-cloud.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class FormCloudService {
+
+ contentTypes = ['application/json']; accepts = ['application/json']; returnType = Object;
+ constructor(
+ private apiService: AlfrescoApiService,
+ private appConfigService: AppConfigService,
+ private logService: LogService
+ ) {}
+
+ getTaskForm(appName: string, taskId: string): Observable {
+ return this.getTask(appName, taskId).pipe(
+ switchMap((task: TaskDetailsCloudModel) => {
+ return this.getForm(appName, task.formKey).pipe(
+ map((form: any) => {
+ form.formRepresentation.taskId = task.id;
+ form.formRepresentation.taskName = task.name;
+ form.formRepresentation.processDefinitionId = task.processDefinitionId;
+ form.formRepresentation.processInstanceId = task.processInstanceId;
+ return form;
+ })
+ );
+ })
+ );
+ }
+
+ saveTaskForm(appName: string, taskId: string, formId: string, formValues: FormValues): Observable {
+ const apiUrl = this.buildSaveFormUrl(appName, formId);
+ const saveFormRepresentation = { values: formValues, taskId: taskId };
+ return from(this.apiService
+ .getInstance()
+ .oauth2Auth.callCustomApi(apiUrl, 'POST',
+ null, null, null,
+ null, saveFormRepresentation,
+ this.contentTypes, this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((res: any) => {
+ return new TaskDetailsCloudModel(res.entry);
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ }
+
+ createTemporaryRawRelatedContent(file, nodeId): Observable {
+
+ const apiUrl = this.buildUploadUrl(nodeId);
+
+ return from(this.apiService
+ .getInstance()
+ .oauth2Auth.callCustomApi(apiUrl, 'POST',
+ null, null, null,
+ { filedata: file, nodeType: 'cm:content' }, null,
+ ['multipart/form-data'], this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((res: any) => {
+ return (res.entry);
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ }
+
+ completeTaskForm(appName: string, taskId: string, formId: string, formValues: FormValues, outcome: string): Observable {
+ const apiUrl = this.buildSubmitFormUrl(appName, formId);
+ const completeFormRepresentation: any = { values: formValues, taskId: taskId };
+ if (outcome) {
+ completeFormRepresentation.outcome = outcome;
+ }
+
+ return from(this.apiService
+ .getInstance()
+ .oauth2Auth.callCustomApi(apiUrl, 'POST',
+ null, null, null,
+ null, completeFormRepresentation,
+ this.contentTypes, this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((res: any) => {
+ return new TaskDetailsCloudModel(res.entry);
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ }
+
+ getTask(appName: string, taskId: string): Observable {
+ const apiUrl = this.buildGetTaskUrl(appName, taskId);
+ return from(this.apiService
+ .getInstance()
+ .oauth2Auth.callCustomApi(apiUrl, 'GET',
+ null, null, null,
+ null, null,
+ this.contentTypes, this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((res: any) => {
+ return new TaskDetailsCloudModel(res.entry);
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ }
+
+ getProcessStorageFolderTask(appName: string, taskId: string): Observable {
+ const apiUrl = this.buildFolderTask(appName, taskId);
+ return from(this.apiService
+ .getInstance()
+ .oauth2Auth.callCustomApi(apiUrl, 'GET',
+ null, null, null,
+ null, null,
+ this.contentTypes, this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((res: any) => {
+ return res.nodeId;
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ }
+
+ getTaskVariables(appName: string, taskId: string): Observable {
+ const apiUrl = this.buildGetTaskVariablesUrl(appName, taskId);
+ return from(this.apiService
+ .getInstance()
+ .oauth2Auth.callCustomApi(apiUrl, 'GET',
+ null, null, null,
+ null, null,
+ this.contentTypes, this.accepts,
+ this.returnType, null, null)
+ ).pipe(
+ map((res: any) => {
+ return res.content;
+ }),
+ catchError((err) => this.handleError(err))
+ );
+ }
+
+ getForm(appName: string, taskId: string): Observable {
+ const apiUrl = this.buildGetFormUrl(appName, taskId);
+ const bodyParam = {}, pathParams = {}, queryParams = {}, headerParams = {},
+ formParams = {};
+
+ return from(
+ this.apiService
+ .getInstance()
+ .oauth2Auth.callCustomApi(
+ apiUrl, 'GET', pathParams, queryParams,
+ headerParams, formParams, bodyParam,
+ this.contentTypes, this.accepts, this.returnType, null, null)
+ ).pipe(
+ catchError((err) => this.handleError(err))
+ );
+ }
+
+ parseForm(json: any, data?: TaskVariableCloud[], readOnly: boolean = false): FormCloud {
+ if (json) {
+ const form = new FormCloud(json, data, readOnly, this);
+ if (!json.fields) {
+ form.outcomes = [
+ new FormOutcomeModel( form, {
+ id: '$save',
+ name: FormOutcomeModel.SAVE_ACTION,
+ isSystem: true
+ })
+ ];
+ }
+ return form;
+ }
+ return null;
+ }
+
+ private buildGetTaskUrl(appName: string, taskId: string): string {
+ return `${this.appConfigService.get('bpmHost')}/${appName}/rb/v1/tasks/${taskId}`;
+ }
+
+ private buildGetFormUrl(appName: string, formId: string): string {
+ return `${this.appConfigService.get('bpmHost')}/${appName}/form/v1/forms/${formId}`;
+ }
+
+ private buildSaveFormUrl(appName: string, formId: string): string {
+ return `${this.appConfigService.get('bpmHost')}/${appName}/form/v1/forms/${formId}/save`;
+ }
+
+ private buildUploadUrl(nodeId: string): string {
+ return `${this.appConfigService.get('ecmHost')}/alfresco/api/-default-/public/alfresco/versions/1/nodes/${nodeId}/children`;
+ }
+
+ private buildSubmitFormUrl(appName: string, formId: string): string {
+ return `${this.appConfigService.get('bpmHost')}/${appName}/form/v1/forms/${formId}/submit`;
+ }
+
+ private buildGetTaskVariablesUrl(appName: string, taskId: string): string {
+ return `${this.appConfigService.get('bpmHost')}/${appName}/rb/v1/tasks/${taskId}/variables`;
+ }
+
+ private buildFolderTask(appName: string, taskId: string): string {
+ return `${this.appConfigService.get('bpmHost')}/${appName}/process-storage/v1/folders/tasks/${taskId}`;
+ }
+
+ private handleError(error: any) {
+ this.logService.error(error);
+ return throwError(error || 'Server error');
+ }
+
+}
diff --git a/lib/process-services-cloud/src/lib/group/group-cloud.module.ts b/lib/process-services-cloud/src/lib/group/group-cloud.module.ts
index 4c28ab8262..7148bce5fd 100644
--- a/lib/process-services-cloud/src/lib/group/group-cloud.module.ts
+++ b/lib/process-services-cloud/src/lib/group/group-cloud.module.ts
@@ -20,7 +20,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
-import { TemplateModule, FormModule, PipeModule, CoreModule } from '@alfresco/adf-core';
+import { TemplateModule, PipeModule, CoreModule } from '@alfresco/adf-core';
import { MaterialModule } from '../material.module';
import { GroupCloudComponent } from './components/group-cloud.component';
import { InitialGroupNamePipe } from './pipe/group-initial.pipe';
@@ -34,7 +34,6 @@ import { InitialGroupNamePipe } from './pipe/group-initial.pipe';
MaterialModule,
FormsModule,
ReactiveFormsModule,
- FormModule,
CoreModule
],
declarations: [GroupCloudComponent, InitialGroupNamePipe],
diff --git a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts
index 2d06817264..248bf6f02e 100644
--- a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts
+++ b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts
@@ -21,6 +21,7 @@ import { AppListCloudModule } from './app/app-list-cloud.module';
import { TaskCloudModule } from './task/task-cloud.module';
import { ProcessCloudModule } from './process/process-cloud.module';
import { GroupCloudModule } from './group/group-cloud.module';
+import { FormCloudModule } from './form/form-cloud.module';
@NgModule({
imports: [
@@ -28,7 +29,8 @@ import { GroupCloudModule } from './group/group-cloud.module';
AppListCloudModule,
ProcessCloudModule,
TaskCloudModule,
- GroupCloudModule
+ GroupCloudModule,
+ FormCloudModule
],
providers: [
{
@@ -44,7 +46,8 @@ import { GroupCloudModule } from './group/group-cloud.module';
AppListCloudModule,
ProcessCloudModule,
TaskCloudModule,
- GroupCloudModule
+ GroupCloudModule,
+ FormCloudModule
]
})
export class ProcessServicesCloudModule { }
diff --git a/lib/process-services-cloud/src/lib/process/process-cloud.module.ts b/lib/process-services-cloud/src/lib/process/process-cloud.module.ts
index 757c16713b..ccecbc4a01 100644
--- a/lib/process-services-cloud/src/lib/process/process-cloud.module.ts
+++ b/lib/process-services-cloud/src/lib/process/process-cloud.module.ts
@@ -20,7 +20,7 @@ import { ProcessFiltersCloudModule } from './process-filters/process-filters-clo
import { ProcessListCloudModule } from './process-list/process-list-cloud.module';
import { StartProcessCloudModule } from './start-process/start-process-cloud.module';
import { CoreModule } from '@alfresco/adf-core';
-import { ProcessHeaderCloudModule } from './process-header/public-api';
+import { ProcessHeaderCloudModule } from './process-header/process-header-cloud.module';
@NgModule({
imports: [
diff --git a/lib/process-services-cloud/src/lib/process/process-header/components/process-header-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-header/components/process-header-cloud.component.ts
index bc60f506f3..d6eddbe24d 100644
--- a/lib/process-services-cloud/src/lib/process/process-header/components/process-header-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/process/process-header/components/process-header-cloud.component.ts
@@ -17,7 +17,7 @@
import { Component, Input, OnChanges } from '@angular/core';
import { CardViewItem, CardViewTextItemModel, TranslationService, AppConfigService, CardViewDateItemModel, CardViewBaseItemModel } from '@alfresco/adf-core';
-import { ProcessInstanceCloud } from '../../start-process/public-api';
+import { ProcessInstanceCloud } from '../../start-process/models/process-instance-cloud.model';
import { ProcessHeaderCloudService } from '../services/process-header-cloud.service';
@Component({
diff --git a/lib/process-services-cloud/src/lib/process/process-header/services/process-header-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-header/services/process-header-cloud.service.ts
index a78a257258..3eb4eedcd4 100644
--- a/lib/process-services-cloud/src/lib/process/process-header/services/process-header-cloud.service.ts
+++ b/lib/process-services-cloud/src/lib/process/process-header/services/process-header-cloud.service.ts
@@ -19,7 +19,7 @@ import { AlfrescoApiService, LogService, AppConfigService } from '@alfresco/adf-
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
-import { ProcessInstanceCloud } from '../../start-process/public-api';
+import { ProcessInstanceCloud } from '../../start-process/models/process-instance-cloud.model';
@Injectable({
providedIn: 'root'
diff --git a/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts b/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts
index dc16962fa0..5244b43b67 100644
--- a/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.ts
@@ -31,7 +31,7 @@ import {
UserPreferenceValues
} from '@alfresco/adf-core';
import { PeopleCloudComponent } from './people-cloud/people-cloud.component';
-import { GroupCloudComponent } from '../../../../lib/group/public-api';
+import { GroupCloudComponent } from '../../../../lib/group/components/group-cloud.component';
@Component({
selector: 'adf-cloud-start-task',
diff --git a/lib/process-services-cloud/src/lib/task/start-task/start-task-cloud.module.ts b/lib/process-services-cloud/src/lib/task/start-task/start-task-cloud.module.ts
index 73138d8666..8535abf100 100644
--- a/lib/process-services-cloud/src/lib/task/start-task/start-task-cloud.module.ts
+++ b/lib/process-services-cloud/src/lib/task/start-task/start-task-cloud.module.ts
@@ -19,7 +19,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MaterialModule } from '../../material.module';
-import { TemplateModule, FormModule, PipeModule, CoreModule } from '@alfresco/adf-core';
+import { TemplateModule, PipeModule, CoreModule } from '@alfresco/adf-core';
import { StartTaskCloudComponent } from './components/start-task-cloud.component';
import { StartTaskCloudService } from './services/start-task-cloud.service';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@@ -36,7 +36,6 @@ import { GroupCloudModule } from '../../group/group-cloud.module';
FormsModule,
ReactiveFormsModule,
GroupCloudModule,
- FormModule,
GroupCloudModule,
CoreModule
],
diff --git a/lib/process-services-cloud/src/lib/task/start-task/testing/start-task-cloud.testing.module.ts b/lib/process-services-cloud/src/lib/task/start-task/testing/start-task-cloud.testing.module.ts
index 2c8281292d..07c142d892 100644
--- a/lib/process-services-cloud/src/lib/task/start-task/testing/start-task-cloud.testing.module.ts
+++ b/lib/process-services-cloud/src/lib/task/start-task/testing/start-task-cloud.testing.module.ts
@@ -21,7 +21,7 @@ import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MaterialModule } from '../../../material.module';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
-import { TemplateModule, TranslateLoaderService, FormModule, PipeModule } from '@alfresco/adf-core';
+import { TemplateModule, TranslateLoaderService, PipeModule } from '@alfresco/adf-core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { StartTaskCloudModule } from '../start-task-cloud.module';
@@ -41,7 +41,6 @@ import { StartTaskCloudModule } from '../start-task-cloud.module';
MaterialModule,
FormsModule,
ReactiveFormsModule,
- FormModule,
PipeModule,
StartTaskCloudModule
]
diff --git a/lib/process-services-cloud/src/public-api.ts b/lib/process-services-cloud/src/public-api.ts
index 37fd3dd798..5bbf0fc44a 100644
--- a/lib/process-services-cloud/src/public-api.ts
+++ b/lib/process-services-cloud/src/public-api.ts
@@ -22,3 +22,4 @@ export * from './lib/process/public-api';
export * from './lib/task/public-api';
export * from './lib/group/public-api';
export * from './lib/services/public-api';
+export * from './lib/form/public-api';
diff --git a/lib/process-services/form/form.component.html b/lib/process-services/form/form.component.html
new file mode 100644
index 0000000000..116db6e634
--- /dev/null
+++ b/lib/process-services/form/form.component.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
diff --git a/lib/core/form/components/form.component.spec.ts b/lib/process-services/form/form.component.spec.ts
similarity index 97%
rename from lib/core/form/components/form.component.spec.ts
rename to lib/process-services/form/form.component.spec.ts
index 5e01552d86..a9bdd4f0ff 100644
--- a/lib/core/form/components/form.component.spec.ts
+++ b/lib/process-services/form/form.component.spec.ts
@@ -16,15 +16,11 @@
*/
import { SimpleChange } from '@angular/core';
-import { LogService } from '../../services/log.service';
import { Observable, of, throwError } from 'rxjs';
-import { fakeForm } from '../../mock';
-import { FormService } from './../services/form.service';
-import { NodeService } from './../services/node.service';
-import { WidgetVisibilityService } from './../services/widget-visibility.service';
+import { FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel,
+ FormService, WidgetVisibilityService, NodeService, LogService, ContainerModel, fakeForm, FormRenderingService } from '@alfresco/adf-core';
+
import { FormComponent } from './form.component';
-import { FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel } from './widgets/index';
-import { ContainerModel } from './widgets/core/container.model';
describe('FormComponent', () => {
@@ -33,6 +29,7 @@ describe('FormComponent', () => {
let visibilityService: WidgetVisibilityService;
let nodeService: NodeService;
let logService: LogService;
+ let formRenderingService: FormRenderingService;
beforeEach(() => {
logService = new LogService(null);
@@ -40,7 +37,8 @@ describe('FormComponent', () => {
spyOn(visibilityService, 'refreshVisibility').and.stub();
formService = new FormService(null, null, logService);
nodeService = new NodeService(null);
- formComponent = new FormComponent(formService, visibilityService, null, nodeService);
+ formRenderingService = new FormRenderingService();
+ formComponent = new FormComponent(formService, visibilityService, null, nodeService, formRenderingService);
});
it('should check form', () => {
@@ -57,13 +55,7 @@ describe('FormComponent', () => {
expect(formModel.taskName).toBe(FormModel.UNSET_TASK_NAME);
expect(formComponent.isTitleEnabled()).toBeTruthy();
- // override property as it's the readonly one
- Object.defineProperty(formModel, 'taskName', {
- enumerable: false,
- configurable: false,
- writable: false,
- value: null
- });
+ formComponent.form = null;
expect(formComponent.isTitleEnabled()).toBeFalsy();
});
diff --git a/lib/core/form/components/form.component.ts b/lib/process-services/form/form.component.ts
similarity index 60%
rename from lib/core/form/components/form.component.ts
rename to lib/process-services/form/form.component.ts
index 169a8a2dfd..d117f11e0c 100644
--- a/lib/core/form/components/form.component.ts
+++ b/lib/process-services/form/form.component.ts
@@ -15,40 +15,24 @@
* limitations under the License.
*/
-/* tslint:disable */
import {
- Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit,
- Output, SimpleChanges, ViewEncapsulation
+ Component, EventEmitter, Input, Output, ViewEncapsulation, SimpleChanges, OnInit, OnDestroy, OnChanges
} from '@angular/core';
-import { FormErrorEvent, FormEvent } from './../events/index';
-import { EcmModelService } from './../services/ecm-model.service';
-import { FormService } from './../services/form.service';
-import { NodeService } from './../services/node.service';
-import { ContentLinkModel } from './widgets/core/content-link.model';
-import {
- FormFieldModel, FormModel, FormOutcomeEvent, FormOutcomeModel,
- FormValues, FormFieldValidator
-} from './widgets/core/index';
-import { Observable, of } from 'rxjs';
-import { WidgetVisibilityService } from './../services/widget-visibility.service';
+import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../content-widget';
+import { EcmModelService, NodeService, WidgetVisibilityService,
+ FormService, FormRenderingService, FormBaseComponent, FormOutcomeModel,
+ ValidateFormEvent, FormEvent, FormErrorEvent, FormFieldModel,
+ FormModel, FormOutcomeEvent, FormValues, ContentLinkModel } from '@alfresco/adf-core';
+
+import { Observable, of, Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
-import { ValidateFormEvent } from './../events/validate-form.event';
-import { Subscription } from 'rxjs';
@Component({
selector: 'adf-form',
templateUrl: './form.component.html',
- styleUrls: ['./form.component.scss'],
encapsulation: ViewEncapsulation.None
})
-export class FormComponent implements OnInit, OnChanges, OnDestroy {
-
- static SAVE_OUTCOME_ID: string = '$save';
- static COMPLETE_OUTCOME_ID: string = '$complete';
- static START_PROCESS_OUTCOME_ID: string = '$startProcess';
- static CUSTOM_OUTCOME_ID: string = '$custom';
- static COMPLETE_BUTTON_COLOR: string = 'primary';
- static COMPLETE_OUTCOME_NAME: string = 'COMPLETE';
+export class FormComponent extends FormBaseComponent implements OnInit, OnDestroy, OnChanges {
/** Underlying form model instance. */
@Input()
@@ -78,54 +62,6 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
@Input()
data: FormValues;
- /** Path of the folder where the metadata will be stored. */
- @Input()
- path: string;
-
- /** Name to assign to the new node where the metadata are stored. */
- @Input()
- nameNode: string;
-
- /** Toggle rendering of the form title. */
- @Input()
- showTitle: boolean = true;
-
- /** Toggle rendering of the `Complete` outcome button. */
- @Input()
- showCompleteButton: boolean = true;
-
- /** If true then the `Complete` outcome button is shown but it will be disabled. */
- @Input()
- disableCompleteButton: boolean = false;
-
- /** If true then the `Start Process` outcome button is shown but it will be disabled. */
- @Input()
- disableStartProcessButton: boolean = false;
-
- /** Toggle rendering of the `Save` outcome button. */
- @Input()
- showSaveButton: boolean = true;
-
- /** Toggle debug options. */
- @Input()
- showDebugButton: boolean = false;
-
- /** Toggle readonly state of the form. Forces all form widgets to render as readonly if enabled. */
- @Input()
- readOnly: boolean = false;
-
- /** Toggle rendering of the `Refresh` button. */
- @Input()
- showRefreshButton: boolean = true;
-
- /** Toggle rendering of the validation icon next to the form title. */
- @Input()
- showValidationIcon: boolean = true;
-
- /** Contains a list of form field validator instances. */
- @Input()
- fieldValidators: FormFieldValidator[] = [];
-
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved: EventEmitter = new EventEmitter();
@@ -146,87 +82,18 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
@Output()
formDataRefreshed: EventEmitter = new EventEmitter();
- /** Emitted when the supplied form values have a validation error.*/
- @Output()
- formError: EventEmitter = new EventEmitter();
-
- /** Emitted when any outcome is executed. Default behaviour can be prevented
- * via `event.preventDefault()`.
- */
- @Output()
- executeOutcome: EventEmitter = new EventEmitter();
-
- /**
- * Emitted when any error occurs.
- */
- @Output()
- error: EventEmitter = new EventEmitter();
-
debugMode: boolean = false;
protected subscriptions: Subscription[] = [];
constructor(protected formService: FormService,
protected visibilityService: WidgetVisibilityService,
- private ecmModelService: EcmModelService,
- private nodeService: NodeService) {
- }
-
- hasForm(): boolean {
- return this.form ? true : false;
- }
-
- isTitleEnabled(): boolean {
- if (this.showTitle) {
- if (this.form && this.form.taskName) {
- return true;
- }
- }
- return false;
- }
-
- getColorForOutcome(outcomeName: string): string {
- return outcomeName === FormComponent.COMPLETE_OUTCOME_NAME ? FormComponent.COMPLETE_BUTTON_COLOR : '';
- }
-
- isOutcomeButtonEnabled(outcome: FormOutcomeModel): boolean {
- if (this.form.readOnly) {
- return false;
- }
-
- if (outcome) {
- // Make 'Save' button always available
- if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
- return true;
- }
- if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
- return this.disableCompleteButton ? false : this.form.isValid;
- }
- if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
- return this.disableStartProcessButton ? false : this.form.isValid;
- }
- return this.form.isValid;
- }
- return false;
- }
-
- isOutcomeButtonVisible(outcome: FormOutcomeModel, isFormReadOnly: boolean): boolean {
- if (outcome && outcome.name) {
- if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
- return this.showCompleteButton;
- }
- if (isFormReadOnly) {
- return outcome.isSelected;
- }
- if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
- return this.showSaveButton;
- }
- if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
- return false;
- }
- return true;
- }
- return false;
+ protected ecmModelService: EcmModelService,
+ protected nodeService: NodeService,
+ protected formRenderingService: FormRenderingService) {
+ super();
+ this.formRenderingService.setComponentTypeResolver('upload', () => AttachFileWidgetComponent, true);
+ this.formRenderingService.setComponentTypeResolver('select-folder', () => AttachFolderWidgetComponent, true);
}
ngOnInit() {
@@ -243,87 +110,42 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}
ngOnDestroy() {
- this.subscriptions.forEach(subscription => subscription.unsubscribe());
+ this.subscriptions.forEach((subscription) => subscription.unsubscribe());
this.subscriptions = [];
}
ngOnChanges(changes: SimpleChanges) {
- let taskId = changes['taskId'];
+ const taskId = changes['taskId'];
if (taskId && taskId.currentValue) {
this.getFormByTaskId(taskId.currentValue);
return;
}
- let formId = changes['formId'];
+ const formId = changes['formId'];
if (formId && formId.currentValue) {
this.getFormDefinitionByFormId(formId.currentValue);
return;
}
- let formName = changes['formName'];
+ const formName = changes['formName'];
if (formName && formName.currentValue) {
this.getFormDefinitionByFormName(formName.currentValue);
return;
}
- let nodeId = changes['nodeId'];
+ const nodeId = changes['nodeId'];
if (nodeId && nodeId.currentValue) {
this.loadFormForEcmNode(nodeId.currentValue);
return;
}
- let data = changes['data'];
+ const data = changes['data'];
if (data && data.currentValue) {
this.refreshFormData();
return;
}
}
- /**
- * Invoked when user clicks outcome button.
- * @param outcome Form outcome model
- */
- onOutcomeClicked(outcome: FormOutcomeModel): boolean {
- if (!this.readOnly && outcome && this.form) {
-
- if (!this.onExecuteOutcome(outcome)) {
- return false;
- }
-
- if (outcome.isSystem) {
- if (outcome.id === FormComponent.SAVE_OUTCOME_ID) {
- this.saveTaskForm();
- return true;
- }
-
- if (outcome.id === FormComponent.COMPLETE_OUTCOME_ID) {
- this.completeTaskForm();
- return true;
- }
-
- if (outcome.id === FormComponent.START_PROCESS_OUTCOME_ID) {
- this.completeTaskForm();
- return true;
- }
-
- if (outcome.id === FormComponent.CUSTOM_OUTCOME_ID) {
- this.onTaskSaved(this.form);
- this.storeFormAsMetadata();
- return true;
- }
- } else {
- // Note: Activiti is using NAME field rather than ID for outcomes
- if (outcome.name) {
- this.onTaskSaved(this.form);
- this.completeTaskForm(outcome.name);
- return true;
- }
- }
- }
-
- return false;
- }
-
/**
* Invoked when user clicks form refresh button.
*/
@@ -370,7 +192,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.formService
.getTaskForm(taskId)
.subscribe(
- form => {
+ (form) => {
const parsedForm = this.parseForm(form);
this.visibilityService.refreshVisibility(parsedForm);
parsedForm.validateForm();
@@ -378,7 +200,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.onFormLoaded(this.form);
resolve(this.form);
},
- error => {
+ (error) => {
this.handleError(error);
// reject(error);
resolve(null);
@@ -392,7 +214,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.formService
.getFormDefinitionById(formId)
.subscribe(
- form => {
+ (form) => {
this.formName = form.name;
this.form = this.parseForm(form);
this.visibilityService.refreshVisibility(this.form);
@@ -409,9 +231,9 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.formService
.getFormDefinitionByName(formName)
.subscribe(
- id => {
+ (id) => {
this.formService.getFormDefinitionById(id).subscribe(
- form => {
+ (form) => {
this.form = this.parseForm(form);
this.visibilityService.refreshVisibility(this.form);
this.form.validateForm();
@@ -437,7 +259,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.onTaskSaved(this.form);
this.storeFormAsMetadata();
},
- error => this.onTaskSavedError(this.form, error)
+ (error) => this.onTaskSavedError(this.form, error)
);
}
}
@@ -451,7 +273,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.onTaskCompleted(this.form);
this.storeFormAsMetadata();
},
- error => this.onTaskCompletedError(this.form, error)
+ (error) => this.onTaskCompletedError(this.form, error)
);
}
}
@@ -462,7 +284,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
parseForm(json: any): FormModel {
if (json) {
- let form = new FormModel(json, this.data, this.readOnly, this.formService);
+ const form = new FormModel(json, this.data, this.readOnly, this.formService);
if (!json.fields) {
form.outcomes = this.getFormDefinitionOutcomes(form);
}
@@ -480,7 +302,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
*/
getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] {
return [
- new FormOutcomeModel(form, { id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true })
+ new FormOutcomeModel(form, { id: '$save', name: FormOutcomeModel.SAVE_ACTION, isSystem: true })
];
}
@@ -497,7 +319,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}
private loadFormForEcmNode(nodeId: string): void {
- this.nodeService.getNodeMetadata(nodeId).subscribe(data => {
+ this.nodeService.getNodeMetadata(nodeId).subscribe((data) => {
this.data = data.metadata;
this.loadFormFromActiviti(data.nodeType);
},
@@ -506,9 +328,9 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
loadFormFromActiviti(nodeType: string): any {
this.formService.searchFrom(nodeType).subscribe(
- form => {
+ (form) => {
if (!form) {
- this.formService.createFormFromANode(nodeType).subscribe(formMetadata => {
+ this.formService.createFormFromANode(nodeType).subscribe((formMetadata) => {
this.loadFormFromFormId(formMetadata.id);
});
} else {
@@ -526,9 +348,9 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
this.loadForm();
}
- private storeFormAsMetadata() {
+ protected storeFormAsMetadata() {
if (this.saveMetadata) {
- this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe(type => {
+ this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe((type) => {
this.nodeService.createNodeMetadata(type.nodeType || type.entry.prefixedName, EcmModelService.MODEL_NAMESPACE, this.form.values, this.path, this.nameNode);
},
(error) => {
@@ -569,7 +391,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}
protected onExecuteOutcome(outcome: FormOutcomeModel): boolean {
- let args = new FormOutcomeEvent(outcome);
+ const args = new FormOutcomeEvent(outcome);
this.formService.executeOutcome.next(args);
if (args.defaultPrevented) {
@@ -583,4 +405,5 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
return true;
}
+
}
diff --git a/lib/core/form/components/form.component.visibility.spec.ts b/lib/process-services/form/form.component.visibility.spec.ts
similarity index 96%
rename from lib/core/form/components/form.component.visibility.spec.ts
rename to lib/process-services/form/form.component.visibility.spec.ts
index 8d5b18f4d2..010f819341 100644
--- a/lib/core/form/components/form.component.visibility.spec.ts
+++ b/lib/process-services/form/form.component.visibility.spec.ts
@@ -22,13 +22,11 @@ import { of } from 'rxjs';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
-import { formDefinitionDropdownField, formDefinitionTwoTextFields, formDefinitionRequiredField } from '../../mock';
-import { formReadonlyTwoTextFields } from '../../mock';
-import { formDefVisibilitiFieldDependsOnNextOne, formDefVisibilitiFieldDependsOnPreviousOne } from '../../mock';
-import { FormService } from './../services/form.service';
+import { formDefinitionDropdownField, formDefinitionTwoTextFields,
+ formDefinitionRequiredField, FormService, setupTestBed, CoreModule,
+ formDefVisibilitiFieldDependsOnNextOne, formDefVisibilitiFieldDependsOnPreviousOne,
+ formReadonlyTwoTextFields } from '@alfresco/adf-core';
import { FormComponent } from './form.component';
-import { setupTestBed } from '../../testing/setupTestBed';
-import { CoreModule } from '../../core.module';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
/** Duration of the select opening animation. */
@@ -54,6 +52,9 @@ describe('FormComponent UI and visibility', () => {
NoopAnimationsModule,
CoreModule.forRoot()
],
+ declarations: [
+ FormComponent
+ ],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
diff --git a/lib/process-services/form/form.module.ts b/lib/process-services/form/form.module.ts
new file mode 100644
index 0000000000..62c1b8e7d8
--- /dev/null
+++ b/lib/process-services/form/form.module.ts
@@ -0,0 +1,38 @@
+/*!
+ * @license
+ * Copyright 2019 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 { NgModule } from '@angular/core';
+import { MaterialModule } from '../material.module';
+import { CoreModule } from '@alfresco/adf-core';
+import { FormComponent } from './form.component';
+import { StartFormComponent } from './start-form.component';
+
+@NgModule({
+ imports: [
+ CoreModule.forChild(),
+ MaterialModule
+ ],
+ declarations: [
+ FormComponent,
+ StartFormComponent
+ ],
+ exports: [
+ FormComponent,
+ StartFormComponent
+ ]
+})
+export class FormModule {}
diff --git a/lib/process-services/form/index.ts b/lib/process-services/form/index.ts
new file mode 100644
index 0000000000..a7e30cc675
--- /dev/null
+++ b/lib/process-services/form/index.ts
@@ -0,0 +1,18 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './public-api';
diff --git a/lib/process-services/form/public-api.ts b/lib/process-services/form/public-api.ts
new file mode 100644
index 0000000000..c8d723ed8f
--- /dev/null
+++ b/lib/process-services/form/public-api.ts
@@ -0,0 +1,20 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './form.component';
+export * from './start-form.component';
+export * from './form.module';
diff --git a/lib/core/form/components/start-form.component.html b/lib/process-services/form/start-form.component.html
similarity index 100%
rename from lib/core/form/components/start-form.component.html
rename to lib/process-services/form/start-form.component.html
diff --git a/lib/process-services/form/start-form.component.scss b/lib/process-services/form/start-form.component.scss
new file mode 100644
index 0000000000..3c747fcf66
--- /dev/null
+++ b/lib/process-services/form/start-form.component.scss
@@ -0,0 +1,125 @@
+@mixin adf-start-form-component-theme($theme) {
+
+ $config: mat-typography-config();
+ $warn: map-get($theme, warn);
+ $accent: map-get($theme, accent);
+
+ .adf {
+ &-form-container {
+ max-width: 100% !important;
+ max-height: 100% !important;
+
+ & .mat-card {
+ padding: 16px 24px;
+ overflow: hidden;
+ }
+
+ & .mat-card-header-text {
+ margin: 0 !important;
+ }
+
+ & .mat-tab-body-content {
+ overflow: hidden;
+ }
+
+ & .mat-tab-label {
+ font-size: mat-font-size($config, subheading-2);
+ line-height: mat-line-height($config, headline);
+ letter-spacing: -0.4px;
+ text-align: left;
+ color: rgba(0, 0, 0, 0.54);
+ text-transform: uppercase;
+ }
+
+ & .mat-ink-bar {
+ height: 4px;
+ }
+
+ & .mat-form-field-wrapper {
+ margin: 0 12px 0 0;
+ }
+ }
+
+ &-form-title {
+ font-size: mat-font-size($alfresco-typography, title);
+ }
+
+ &-form-debug-container {
+ padding: 10px;
+ }
+
+ &-form-debug-container .adf-debug-toggle-text {
+ padding-left: 15px;
+ cursor: pointer;
+ }
+
+ &-form-debug-container .adf-debug-toggle-text:hover {
+ font-weight: bold;
+ }
+
+ &-form-reload-button {
+ position: absolute;
+ right: 12px;
+ top: 30px;
+ }
+
+ &-form-validation-button {
+ position: absolute;
+ right: 50px;
+ top: 39px;
+ color: mat-color($accent);
+
+ & .adf-invalid-color {
+ color: mat-color($warn);
+ }
+ }
+
+ &-form-hide-button {
+ display: none !important;
+ }
+
+ &-task-title {
+ text-align: center;
+ }
+
+ &-label {
+ width: 32px;
+ height: 16px;
+ font-size: mat-font-size($config, caption);
+ line-height: mat-line-height($config, headline);
+ text-align: left;
+ white-space: nowrap;
+ }
+
+ &-form-mat-card-actions {
+ float: right;
+ padding-bottom: 25px !important;
+ padding-right: 25px !important;
+
+ & .mat-button {
+ height: 36px;
+ border-radius: 5px;
+
+ }
+
+ & .mat-button-wrapper {
+ width: 58px;
+ height: 20px;
+ opacity: 0.54;
+ font-size: mat-font-size($config, body-2);
+ font-weight: bold;
+ }
+ }
+ }
+
+ form-field {
+ width: 100%;
+
+ .mat-input-element {
+ font-size: mat-font-size($config, body-2);
+ padding-top: 8px;
+ line-height: normal;
+ }
+ }
+
+}
diff --git a/lib/core/form/components/start-form.component.spec.ts b/lib/process-services/form/start-form.component.spec.ts
similarity index 98%
rename from lib/core/form/components/start-form.component.spec.ts
rename to lib/process-services/form/start-form.component.spec.ts
index 851e8f8006..c994396fae 100644
--- a/lib/core/form/components/start-form.component.spec.ts
+++ b/lib/process-services/form/start-form.component.spec.ts
@@ -18,15 +18,11 @@
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
-import { startFormDateWidgetMock, startFormDropdownDefinitionMock, startFormTextDefinitionMock, startMockForm, startMockFormWithTab } from '../../mock';
-import { startFormAmountWidgetMock, startFormNumberWidgetMock, startFormRadioButtonWidgetMock } from '../../mock';
-import { FormService } from './../services/form.service';
-import { WidgetVisibilityService } from './../services/widget-visibility.service';
+import { startFormDateWidgetMock, startFormDropdownDefinitionMock, startFormTextDefinitionMock, startMockForm, startMockFormWithTab } from '../../core/mock';
+import { startFormAmountWidgetMock, startFormNumberWidgetMock, startFormRadioButtonWidgetMock } from '../../core/mock';
import { StartFormComponent } from './start-form.component';
-import { FormModel, FormOutcomeModel } from './widgets/index';
-import { setupTestBed } from '../../testing/setupTestBed';
-import { CoreModule } from '../../core.module';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { FormService, WidgetVisibilityService, setupTestBed, CoreModule, FormModel, FormOutcomeModel } from '@alfresco/adf-core';
describe('StartFormComponent', () => {
@@ -44,6 +40,9 @@ describe('StartFormComponent', () => {
NoopAnimationsModule,
CoreModule.forRoot()
],
+ declarations: [
+ StartFormComponent
+ ],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
diff --git a/lib/core/form/components/start-form.component.ts b/lib/process-services/form/start-form.component.ts
similarity index 92%
rename from lib/core/form/components/start-form.component.ts
rename to lib/process-services/form/start-form.component.ts
index ed97a64fe4..71db078234 100644
--- a/lib/core/form/components/start-form.component.ts
+++ b/lib/process-services/form/start-form.component.ts
@@ -28,17 +28,13 @@ import {
ViewEncapsulation,
OnDestroy
} from '@angular/core';
-import { FormService } from './../services/form.service';
-import { WidgetVisibilityService } from './../services/widget-visibility.service';
import { FormComponent } from './form.component';
-import { ContentLinkModel } from './widgets/core/content-link.model';
-import { FormOutcomeModel } from './widgets/core/index';
-import { ValidateFormEvent } from './../events/validate-form.event';
+import { ContentLinkModel, FormService, WidgetVisibilityService, FormRenderingService, ValidateFormEvent, FormOutcomeModel } from '@alfresco/adf-core';
@Component({
selector: 'adf-start-form',
templateUrl: './start-form.component.html',
- styleUrls: ['./form.component.scss'],
+ styleUrls: ['./start-form.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class StartFormComponent extends FormComponent implements OnChanges, OnInit, OnDestroy {
@@ -75,8 +71,9 @@ export class StartFormComponent extends FormComponent implements OnChanges, OnIn
outcomesContainer: ElementRef = null;
constructor(formService: FormService,
- visibilityService: WidgetVisibilityService) {
- super(formService, visibilityService, null, null);
+ visibilityService: WidgetVisibilityService,
+ formRenderingService: FormRenderingService) {
+ super(formService, visibilityService, null, null, formRenderingService);
this.showTitle = false;
}
diff --git a/lib/process-services/index.ts b/lib/process-services/index.ts
index bb1992f733..c392ed639f 100644
--- a/lib/process-services/index.ts
+++ b/lib/process-services/index.ts
@@ -22,5 +22,6 @@ export * from './attachment/index';
export * from './process-comments/index';
export * from './people/index';
export * from './content-widget/index';
+export * from './form/index';
export * from './process.module';
diff --git a/lib/process-services/process-list/components/process-instance-details.component.spec.ts b/lib/process-services/process-list/components/process-instance-details.component.spec.ts
index ae23a9baae..31352bcde0 100644
--- a/lib/process-services/process-list/components/process-instance-details.component.spec.ts
+++ b/lib/process-services/process-list/components/process-instance-details.component.spec.ts
@@ -20,7 +20,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
-import { FormModule, setupTestBed } from '@alfresco/adf-core';
+import { setupTestBed } from '@alfresco/adf-core';
import { TaskListModule } from '../../task-list/task-list.module';
import { ProcessInstance } from '../models/process-instance.model';
@@ -28,6 +28,7 @@ import { exampleProcess, exampleProcessNoName } from './../../mock';
import { ProcessService } from './../services/process.service';
import { ProcessInstanceDetailsComponent } from './process-instance-details.component';
import { ProcessTestingModule } from '../../testing/process.testing.module';
+import { FormModule } from '../../form';
describe('ProcessInstanceDetailsComponent', () => {
diff --git a/lib/process-services/process-list/components/start-process.component.ts b/lib/process-services/process-list/components/start-process.component.ts
index a4dc0e0ac8..d30431d0d5 100644
--- a/lib/process-services/process-list/components/start-process.component.ts
+++ b/lib/process-services/process-list/components/start-process.component.ts
@@ -21,17 +21,17 @@ import {
} from '@angular/core';
import {
ActivitiContentService, AppConfigService, AppConfigValues,
- StartFormComponent, FormRenderingService, FormValues
+ FormValues
} from '@alfresco/adf-core';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { ProcessDefinitionRepresentation } from './../models/process-definition.model';
import { ProcessInstance } from './../models/process-instance.model';
import { ProcessService } from './../services/process.service';
-import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../content-widget';
import { FormControl, Validators, AbstractControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { MatAutocompleteTrigger } from '@angular/material';
+import { StartFormComponent } from '../../form';
@Component({
selector: 'adf-start-process',
@@ -102,12 +102,9 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit {
maxProcessNameLength: number = this.MAX_LENGTH;
constructor(private activitiProcess: ProcessService,
- private formRenderingService: FormRenderingService,
private activitiContentService: ActivitiContentService,
private appConfig: AppConfigService) {
- this.formRenderingService.setComponentTypeResolver('upload', () => AttachFileWidgetComponent, true);
- this.formRenderingService.setComponentTypeResolver('select-folder', () => AttachFolderWidgetComponent, true);
- }
+ }
ngOnInit() {
this.processNameInput = new FormControl(this.name, [Validators.required, Validators.maxLength(this.maxProcessNameLength)]);
diff --git a/lib/process-services/process-list/process-list.module.ts b/lib/process-services/process-list/process-list.module.ts
index 60a844896a..f4d6c1340c 100644
--- a/lib/process-services/process-list/process-list.module.ts
+++ b/lib/process-services/process-list/process-list.module.ts
@@ -33,6 +33,7 @@ import { ProcessInstanceHeaderComponent } from './components/process-instance-he
import { ProcessInstanceTasksComponent } from './components/process-instance-tasks.component';
import { ProcessInstanceListComponent } from './components/process-list.component';
import { StartProcessInstanceComponent } from './components/start-process.component';
+import { FormModule } from '../form/form.module';
@NgModule({
imports: [
@@ -45,7 +46,8 @@ import { StartProcessInstanceComponent } from './components/start-process.compon
TaskListModule,
PeopleModule,
ContentWidgetModule,
- ProcessCommentsModule
+ ProcessCommentsModule,
+ FormModule
],
declarations: [
ProcessInstanceListComponent,
diff --git a/lib/process-services/process.module.ts b/lib/process-services/process.module.ts
index 26f42dce78..58de03ae24 100644
--- a/lib/process-services/process.module.ts
+++ b/lib/process-services/process.module.ts
@@ -28,6 +28,7 @@ import { AppsListModule } from './app-list/apps-list.module';
import { ProcessCommentsModule } from './process-comments/process-comments.module';
import { AttachmentModule } from './attachment/attachment.module';
import { PeopleModule } from './people/people.module';
+import { FormModule } from './form/form.module';
@NgModule({
imports: [
@@ -41,7 +42,8 @@ import { PeopleModule } from './people/people.module';
TaskListModule,
AppsListModule,
AttachmentModule,
- PeopleModule
+ PeopleModule,
+ FormModule
],
providers: [
{
@@ -62,7 +64,8 @@ import { PeopleModule } from './people/people.module';
TaskListModule,
AppsListModule,
AttachmentModule,
- PeopleModule
+ PeopleModule,
+ FormModule
]
})
export class ProcessModule {
diff --git a/lib/process-services/styles/_index.scss b/lib/process-services/styles/_index.scss
index a7d72ab228..40dc42af32 100644
--- a/lib/process-services/styles/_index.scss
+++ b/lib/process-services/styles/_index.scss
@@ -9,6 +9,7 @@
@import '../task-list/components/task-standalone.component';
@import '../app-list/apps-list.component';
@import '../content-widget/attach-file-widget-dialog.component';
+@import '../form/start-form.component';
@mixin adf-process-services-theme($theme) {
@include adf-process-filters-theme($theme);
@@ -22,4 +23,5 @@
@include adf-apps-theme($theme);
@include adf-task-standalone-component-theme($theme);
@include adf-attach-file-widget-dialog-component-theme($theme);
-}
+ @include adf-start-form-component-theme($theme);
+ }
diff --git a/lib/process-services/task-list/components/no-task-detail-template.directive.spec.ts b/lib/process-services/task-list/components/no-task-detail-template.directive.spec.ts
index 61188986bf..4322e1b691 100644
--- a/lib/process-services/task-list/components/no-task-detail-template.directive.spec.ts
+++ b/lib/process-services/task-list/components/no-task-detail-template.directive.spec.ts
@@ -17,7 +17,7 @@
import { NoTaskDetailsTemplateDirective } from './no-task-detail-template.directive';
import { TaskDetailsComponent } from './task-details.component';
-import { FormRenderingService, AuthenticationService } from '@alfresco/adf-core';
+import { AuthenticationService } from '@alfresco/adf-core';
import { of } from 'rxjs';
describe('NoTaskDetailsTemplateDirective', () => {
@@ -29,7 +29,7 @@ describe('NoTaskDetailsTemplateDirective', () => {
beforeEach(() => {
authService = new AuthenticationService(null, null, null, null);
spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ email: 'fake-email'}));
- detailsComponent = new TaskDetailsComponent(null, authService, null, new FormRenderingService(), null, null, null);
+ detailsComponent = new TaskDetailsComponent(null, authService, null, null, null, null);
component = new NoTaskDetailsTemplateDirective(detailsComponent);
});
diff --git a/lib/process-services/task-list/components/task-details.component.html b/lib/process-services/task-list/components/task-details.component.html
index d5019be012..ac86217905 100644
--- a/lib/process-services/task-list/components/task-details.component.html
+++ b/lib/process-services/task-list/components/task-details.component.html
@@ -23,7 +23,6 @@