mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-3797] Task management view - Task with Form (#4534)
* [ADF-4248] Created form cloud service * [ADF-4248] Created form cloud model * [ADF-4248] Created new cloud form * [ADF-4248] Exported cloud from module * [ADF-4248] Added form saving feature * [ADF-4248] Added form to task details * [ADF-4248] Added services to save form * [ADF-4248] Added data support * [ADF-4248] Added outcome support in form model * [ADF-4248] Modified demo component to show form * [ADF-4248] Copied tests * [ADF-4248] Added form parsing service * [ADF-4248] Added form cloud demo * [ADF-4248] Added form input to fom-cloud * [ADF-4248] Added tests for form cloud model * [ADF-4248] Improved form model json parsing * [ADF-4248] Added test for form could * [ADF-4248] Refactored types in the form model * [ADF-4248] Improved tests * [ADF-4248] Added tests for form cloud service * [ADF-4248] Added tests for form services * [ADF-4248] Refactored form services * [ADF-4248] Handled form events in demo shell * [ADF-4248] Improved form value parsing * [ADF-4248] Added form-cloud demo to routing * [ADF-4248] Added field validation without handler * [ADF-4248] Added task variable model * [ADF-4248] Added adf-cloud prefix to css classes * [ADF-4248] Translated name of nameless task * [ADF-4248] Added docs for cloud form component * [ADF-4248] Added docs for cloud form service * create base component * [ADF-4248] Created formBase and formModelbase * [ADF-4248] Used base classes in cloud package * Update form-cloud.component.md * Update form-cloud.service.md * [ADF-4248] Created form cloud service * [ADF-4248] Created form cloud model * [ADF-4248] Created new cloud form * [ADF-4248] Exported cloud from module * [ADF-4248] Added form saving feature * [ADF-4248] Added form to task details * [ADF-4248] Added services to save form * [ADF-4248] Added data support * [ADF-4248] Added outcome support in form model * [ADF-4248] Modified demo component to show form * [ADF-4248] Copied tests * [ADF-4248] Added form parsing service * [ADF-4248] Added form cloud demo * [ADF-4248] Added form input to fom-cloud * [ADF-4248] Added tests for form cloud model * [ADF-4248] Improved form model json parsing * [ADF-4248] Added test for form could * [ADF-4248] Refactored types in the form model * [ADF-4248] Improved tests * [ADF-4248] Added tests for form cloud service * [ADF-4248] Added tests for form services * [ADF-4248] Refactored form services * [ADF-4248] Handled form events in demo shell * [ADF-4248] Improved form value parsing * [ADF-4248] Added form-cloud demo to routing * [ADF-4248] Added field validation without handler * [ADF-4248] Added task variable model * [ADF-4248] Added adf-cloud prefix to css classes * [ADF-4248] Translated name of nameless task * [ADF-4248] Added docs for cloud form component * [ADF-4248] Added docs for cloud form service * create base component * [ADF-4248] Created formBase and formModelbase * [ADF-4248] Used base classes in cloud package * [ADF-4248] Moved documentation to process services * [ADF-4248] Removed duplicate import * [ADF-4248] Fixed wrong imports * [ADF-4248] Renamed form renderer input * [ADF-4248] Show translated name for nameless form * Enable the uploadWidget * Make the form great again! * Move the class style on the parent * Fix the debugMode
This commit is contained in:
committed by
Eugenio Romano
parent
61ee1f1d53
commit
558ee4c031
213
lib/core/form/components/form-base.component.ts
Normal file
213
lib/core/form/components/form-base.component.ts
Normal file
@@ -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<FormFieldModel[]> = new EventEmitter<FormFieldModel[]>();
|
||||
|
||||
/** Emitted when any outcome is executed. Default behaviour can be prevented
|
||||
* via `event.preventDefault()`.
|
||||
*/
|
||||
@Output()
|
||||
executeOutcome: EventEmitter<FormOutcomeEvent> = new EventEmitter<FormOutcomeEvent>();
|
||||
|
||||
/**
|
||||
* Emitted when any error occurs.
|
||||
*/
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
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);
|
||||
}
|
84
lib/core/form/components/form-base.model.ts
Normal file
84
lib/core/form/components/form-base.model.ts
Normal file
@@ -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 = <ContainerModel> 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();
|
||||
}
|
25
lib/core/form/components/form-renderer.component.html
Normal file
25
lib/core/form/components/form-renderer.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="{{formDefinition.className}}" [ngClass]="{'adf-readonly-form': formDefinition.readOnly }">
|
||||
<div *ngIf="formDefinition.hasTabs()">
|
||||
<tabs-widget [tabs]="formDefinition.tabs"></tabs-widget>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!formDefinition.hasTabs() && formDefinition.hasFields()">
|
||||
<div *ngFor="let field of formDefinition.fields">
|
||||
<adf-form-field [field]="field.field"></adf-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
For debugging and data visualisation purposes,
|
||||
will be removed during future revisions
|
||||
-->
|
||||
<div *ngIf="showDebugButton" class="adf-form-debug-container">
|
||||
<mat-slide-toggle [(ngModel)]="debugMode">Debug mode</mat-slide-toggle>
|
||||
<div *ngIf="debugMode">
|
||||
<h4>Values</h4>
|
||||
<pre>{{formDefinition.values | json}}</pre>
|
||||
|
||||
<h4>Form</h4>
|
||||
<pre>{{formDefinition.json | json}}</pre>
|
||||
</div>
|
||||
</div>
|
@@ -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);
|
38
lib/core/form/components/form-renderer.component.ts
Normal file
38
lib/core/form/components/form-renderer.component.ts
Normal file
@@ -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;
|
||||
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
<div *ngIf="!hasForm()">
|
||||
<ng-content select="[empty-form]">
|
||||
</ng-content>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasForm()" class="{{form.className}} adf-form-container" [ngClass]="{'adf-readonly-form': readOnly }">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<h4>
|
||||
<div *ngIf="showValidationIcon" class="adf-form-validation-button">
|
||||
<i id="adf-valid-form-icon" class="material-icons" *ngIf="form.isValid; else no_valid_form">check_circle</i>
|
||||
<ng-template #no_valid_form>
|
||||
<i id="adf-invalid-form-icon" class="material-icons adf-invalid-color">error</i>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="showRefreshButton" class="adf-form-reload-button">
|
||||
<button mat-icon-button (click)="onRefreshClicked()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<span *ngIf="isTitleEnabled()" class="adf-form-title">{{form.taskName}}</span>
|
||||
|
||||
</h4>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div *ngIf="form.hasTabs()">
|
||||
<tabs-widget [tabs]="form.tabs"></tabs-widget>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!form.hasTabs() && form.hasFields()">
|
||||
<div *ngFor="let field of form.fields">
|
||||
<adf-form-field [field]="field.field"></adf-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<mat-card-actions *ngIf="form.hasOutcomes()" class="adf-form-mat-card-actions">
|
||||
<!--[class.mdl-button--colored]="!outcome.isSystem"-->
|
||||
<button [id]="'adf-form-'+ outcome.name | formatSpace" *ngFor="let outcome of form.outcomes"
|
||||
[color]="getColorForOutcome(outcome.name)"
|
||||
mat-button
|
||||
[disabled]="!isOutcomeButtonEnabled(outcome)"
|
||||
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
|
||||
(click)="onOutcomeClicked(outcome)">
|
||||
{{outcome.name | translate | uppercase }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
<!--
|
||||
For debugging and data visualisation purposes,
|
||||
will be removed during future revisions
|
||||
-->
|
||||
<div *ngIf="showDebugButton" class="adf-form-debug-container">
|
||||
<mat-slide-toggle [(ngModel)]="debugMode">Debug mode</mat-slide-toggle>
|
||||
<div *ngIf="debugMode && hasForm()">
|
||||
<h4>Values</h4>
|
||||
<pre>{{form.values | json}}</pre>
|
||||
|
||||
<h4>Form</h4>
|
||||
<pre>{{form.json | json}}</pre>
|
||||
</div>
|
||||
</div>
|
@@ -1,863 +0,0 @@
|
||||
/*!
|
||||
* @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 { 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 { FormComponent } from './form.component';
|
||||
import { FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel } from './widgets/index';
|
||||
import { ContainerModel } from './widgets/core/container.model';
|
||||
|
||||
describe('FormComponent', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let formComponent: FormComponent;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
let nodeService: NodeService;
|
||||
let logService: LogService;
|
||||
|
||||
beforeEach(() => {
|
||||
logService = new LogService(null);
|
||||
visibilityService = new WidgetVisibilityService(null, logService);
|
||||
spyOn(visibilityService, 'refreshVisibility').and.stub();
|
||||
formService = new FormService(null, null, logService);
|
||||
nodeService = new NodeService(null);
|
||||
formComponent = new FormComponent(formService, visibilityService, null, nodeService);
|
||||
});
|
||||
|
||||
it('should check form', () => {
|
||||
expect(formComponent.hasForm()).toBeFalsy();
|
||||
formComponent.form = new FormModel();
|
||||
expect(formComponent.hasForm()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow title if task name available', () => {
|
||||
const formModel = new FormModel();
|
||||
formComponent.form = formModel;
|
||||
|
||||
expect(formComponent.showTitle).toBeTruthy();
|
||||
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
|
||||
});
|
||||
|
||||
expect(formComponent.isTitleEnabled()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not allow title', () => {
|
||||
const formModel = new FormModel();
|
||||
|
||||
formComponent.form = formModel;
|
||||
formComponent.showTitle = false;
|
||||
|
||||
expect(formModel.taskName).toBe(FormModel.UNSET_TASK_NAME);
|
||||
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 FormModel();
|
||||
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 FormModel();
|
||||
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 FormModel();
|
||||
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 FormModel();
|
||||
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 FormModel({ 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 FormModel();
|
||||
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 form by task id on load', () => {
|
||||
spyOn(formComponent, 'getFormByTaskId').and.stub();
|
||||
|
||||
const taskId = '123';
|
||||
|
||||
formComponent.taskId = taskId;
|
||||
formComponent.loadForm();
|
||||
|
||||
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
|
||||
});
|
||||
|
||||
it('should get process variable if is a process task', () => {
|
||||
spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ taskId: currentTaskId });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(of({}));
|
||||
spyOn(formService, 'getTask').and.callFake((currentTaskId) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ taskId: currentTaskId, processDefinitionId: '10201' });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
const taskId = '123';
|
||||
|
||||
formComponent.taskId = taskId;
|
||||
formComponent.loadForm();
|
||||
|
||||
expect(visibilityService.getTaskProcessVariable).toHaveBeenCalledWith(taskId);
|
||||
});
|
||||
|
||||
it('should not get process variable if is not a process task', () => {
|
||||
spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ taskId: currentTaskId });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(of({}));
|
||||
spyOn(formService, 'getTask').and.callFake((currentTaskId) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ taskId: currentTaskId, processDefinitionId: 'null' });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
const taskId = '123';
|
||||
|
||||
formComponent.taskId = taskId;
|
||||
formComponent.loadForm();
|
||||
|
||||
expect(visibilityService.getTaskProcessVariable).toHaveBeenCalledWith(taskId);
|
||||
});
|
||||
|
||||
it('should get form definition by form id on load', () => {
|
||||
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
|
||||
const formId = 123;
|
||||
|
||||
formComponent.formId = formId;
|
||||
formComponent.loadForm();
|
||||
|
||||
expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId);
|
||||
});
|
||||
|
||||
it('should refresh visibility when the form is loaded', () => {
|
||||
spyOn(formService, 'getFormDefinitionById').and.returnValue(of(JSON.parse(JSON.stringify(fakeForm))));
|
||||
const formId = 123;
|
||||
|
||||
formComponent.formId = formId;
|
||||
formComponent.loadForm();
|
||||
|
||||
expect(formService.getFormDefinitionById).toHaveBeenCalledWith(formId);
|
||||
expect(visibilityService.refreshVisibility).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should get form definition by form name on load', () => {
|
||||
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
|
||||
const formName = '<form>';
|
||||
|
||||
formComponent.formName = formName;
|
||||
formComponent.loadForm();
|
||||
|
||||
expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName);
|
||||
});
|
||||
|
||||
it('should reload form by task id on binding changes', () => {
|
||||
spyOn(formComponent, 'getFormByTaskId').and.stub();
|
||||
const taskId = '<task id>';
|
||||
|
||||
const change = new SimpleChange(null, taskId, true);
|
||||
formComponent.ngOnChanges({ 'taskId': change });
|
||||
|
||||
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
|
||||
});
|
||||
|
||||
it('should reload form definition by form id on binding changes', () => {
|
||||
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
|
||||
const formId = '123';
|
||||
|
||||
const change = new SimpleChange(null, formId, true);
|
||||
formComponent.ngOnChanges({ 'formId': change });
|
||||
|
||||
expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId);
|
||||
});
|
||||
|
||||
it('should reload form definition by name on binding changes', () => {
|
||||
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
|
||||
const formName = '<form>';
|
||||
|
||||
const change = new SimpleChange(null, formName, true);
|
||||
formComponent.ngOnChanges({ 'formName': change });
|
||||
|
||||
expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName);
|
||||
});
|
||||
|
||||
it('should not get form on load', () => {
|
||||
spyOn(formComponent, 'getFormByTaskId').and.stub();
|
||||
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
|
||||
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
|
||||
|
||||
formComponent.taskId = null;
|
||||
formComponent.formId = null;
|
||||
formComponent.formName = null;
|
||||
formComponent.loadForm();
|
||||
|
||||
expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
|
||||
expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled();
|
||||
expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reload form on binding changes', () => {
|
||||
spyOn(formComponent, 'getFormByTaskId').and.stub();
|
||||
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
|
||||
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
|
||||
|
||||
formComponent.ngOnChanges({ 'tag': new SimpleChange(null, 'hello world', true) });
|
||||
|
||||
expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
|
||||
expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled();
|
||||
expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should complete form on custom outcome click', () => {
|
||||
const formModel = new FormModel();
|
||||
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 FormModel();
|
||||
const outcome = new FormOutcomeModel(formModel, {
|
||||
id: FormComponent.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 FormModel();
|
||||
const outcome = new FormOutcomeModel(formModel, {
|
||||
id: FormComponent.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 FormModel();
|
||||
const outcome = new FormOutcomeModel(formModel, {
|
||||
id: FormComponent.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 FormModel();
|
||||
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 FormModel();
|
||||
formComponent.readOnly = false;
|
||||
expect(formComponent.onOutcomeClicked(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require loaded form when clicking outcome', () => {
|
||||
const formModel = new FormModel();
|
||||
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 FormModel();
|
||||
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 FormModel();
|
||||
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) => {
|
||||
spyOn(formService, 'getTask').and.returnValue(of({}));
|
||||
spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ taskId: currentTaskId });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
const taskId = '456';
|
||||
formComponent.formLoaded.subscribe(() => {
|
||||
expect(formService.getTaskForm).toHaveBeenCalledWith(taskId);
|
||||
expect(formComponent.form).toBeDefined();
|
||||
expect(formComponent.form.taskId).toBe(taskId);
|
||||
done();
|
||||
});
|
||||
|
||||
expect(formComponent.form).toBeUndefined();
|
||||
formComponent.getFormByTaskId(taskId);
|
||||
});
|
||||
|
||||
it('should handle error when getting form by task id', (done) => {
|
||||
const error = 'Some error';
|
||||
|
||||
spyOn(formService, 'getTask').and.returnValue(of({}));
|
||||
spyOn(formComponent, 'handleError').and.stub();
|
||||
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
|
||||
return throwError(error);
|
||||
});
|
||||
|
||||
formComponent.getFormByTaskId('123').then((_) => {
|
||||
expect(formComponent.handleError).toHaveBeenCalledWith(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply readonly state when getting form by task id', (done) => {
|
||||
spyOn(formService, 'getTask').and.returnValue(of({}));
|
||||
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ taskId: taskId });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
formComponent.readOnly = true;
|
||||
formComponent.getFormByTaskId('123').then((_) => {
|
||||
expect(formComponent.form).toBeDefined();
|
||||
expect(formComponent.form.readOnly).toBe(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch and parse form definition by id', () => {
|
||||
spyOn(formService, 'getFormDefinitionById').and.callFake((currentFormId) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ id: currentFormId });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
const formId = 456;
|
||||
let loaded = false;
|
||||
formComponent.formLoaded.subscribe(() => loaded = true);
|
||||
|
||||
expect(formComponent.form).toBeUndefined();
|
||||
formComponent.getFormDefinitionByFormId(formId);
|
||||
|
||||
expect(loaded).toBeTruthy();
|
||||
expect(formComponent.form).toBeDefined();
|
||||
expect(formComponent.form.id).toBe(formId);
|
||||
});
|
||||
|
||||
it('should handle error when getting form by definition id', () => {
|
||||
const error = 'Some error';
|
||||
|
||||
spyOn(formComponent, 'handleError').and.stub();
|
||||
spyOn(formService, 'getFormDefinitionById').and.callFake(() => throwError(error));
|
||||
|
||||
formComponent.getFormDefinitionByFormId(123);
|
||||
expect(formComponent.handleError).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should fetch and parse form definition by form name', () => {
|
||||
spyOn(formService, 'getFormDefinitionByName').and.callFake((currentFormName) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next(currentFormName);
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
spyOn(formService, 'getFormDefinitionById').and.callFake((currentFormName) => {
|
||||
return new Observable((observer) => {
|
||||
observer.next({ name: currentFormName });
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
|
||||
const formName = '<form>';
|
||||
let loaded = false;
|
||||
formComponent.formLoaded.subscribe(() => loaded = true);
|
||||
|
||||
expect(formComponent.form).toBeUndefined();
|
||||
formComponent.getFormDefinitionByFormName(formName);
|
||||
|
||||
expect(loaded).toBeTruthy();
|
||||
expect(formService.getFormDefinitionByName).toHaveBeenCalledWith(formName);
|
||||
expect(formComponent.form).toBeDefined();
|
||||
expect(formComponent.form.name).toBe(formName);
|
||||
});
|
||||
|
||||
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 formModel = new FormModel({
|
||||
taskId: '123',
|
||||
fields: [
|
||||
{ id: 'field1' },
|
||||
{ id: 'field2' }
|
||||
]
|
||||
});
|
||||
formComponent.form = formModel;
|
||||
formComponent.saveTaskForm();
|
||||
|
||||
expect(formService.saveTaskForm).toHaveBeenCalledWith(formModel.taskId, 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();
|
||||
|
||||
formComponent.form = new FormModel({ taskId: '123' });
|
||||
formComponent.saveTaskForm();
|
||||
|
||||
expect(formComponent.handleError).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should require form with task id to save', () => {
|
||||
spyOn(formService, 'saveTaskForm').and.stub();
|
||||
|
||||
formComponent.form = null;
|
||||
formComponent.saveTaskForm();
|
||||
|
||||
formComponent.form = new FormModel();
|
||||
formComponent.saveTaskForm();
|
||||
|
||||
expect(formService.saveTaskForm).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require form with task id to complete', () => {
|
||||
spyOn(formService, 'completeTaskForm').and.stub();
|
||||
|
||||
formComponent.form = null;
|
||||
formComponent.completeTaskForm('save');
|
||||
|
||||
formComponent.form = new FormModel();
|
||||
formComponent.completeTaskForm('complete');
|
||||
|
||||
expect(formService.completeTaskForm).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should complete form 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 formModel = new FormModel({
|
||||
taskId: '123',
|
||||
fields: [
|
||||
{ id: 'field1' },
|
||||
{ id: 'field2' }
|
||||
]
|
||||
});
|
||||
|
||||
formComponent.form = formModel;
|
||||
formComponent.completeTaskForm(outcome);
|
||||
|
||||
expect(formService.completeTaskForm).toHaveBeenCalledWith(formModel.taskId, 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({
|
||||
id: 1,
|
||||
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({ id: 1 });
|
||||
expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form);
|
||||
});
|
||||
|
||||
it('should prevent default outcome execution', () => {
|
||||
|
||||
const outcome = new FormOutcomeModel(new FormModel(), {
|
||||
id: FormComponent.CUSTOM_OUTCOME_ID,
|
||||
name: 'Custom'
|
||||
});
|
||||
|
||||
formComponent.form = new FormModel();
|
||||
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 FormModel(), {
|
||||
id: FormComponent.CUSTOM_OUTCOME_ID,
|
||||
name: 'Custom'
|
||||
});
|
||||
|
||||
formComponent.form = new FormModel();
|
||||
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 FormModel());
|
||||
formComponent.checkVisibility(field);
|
||||
expect(visibilityService.refreshVisibility).toHaveBeenCalledWith(field.form);
|
||||
});
|
||||
|
||||
it('should load form for ecm node', () => {
|
||||
const metadata = {};
|
||||
spyOn(nodeService, 'getNodeMetadata').and.returnValue(
|
||||
new Observable((observer) => {
|
||||
observer.next({ metadata: metadata });
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
spyOn(formComponent, 'loadFormFromActiviti').and.stub();
|
||||
|
||||
const nodeId = '<id>';
|
||||
const change = new SimpleChange(null, nodeId, false);
|
||||
formComponent.ngOnChanges({ 'nodeId': change });
|
||||
|
||||
expect(nodeService.getNodeMetadata).toHaveBeenCalledWith(nodeId);
|
||||
expect(formComponent.loadFormFromActiviti).toHaveBeenCalled();
|
||||
expect(formComponent.data).toBe(metadata);
|
||||
});
|
||||
|
||||
it('should disable outcome buttons for readonly form', () => {
|
||||
const formModel = new FormModel();
|
||||
formModel.readOnly = true;
|
||||
formComponent.form = formModel;
|
||||
|
||||
const outcome = new FormOutcomeModel(new FormModel(), {
|
||||
id: FormComponent.CUSTOM_OUTCOME_ID,
|
||||
name: 'Custom'
|
||||
});
|
||||
|
||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require outcome to eval button state', () => {
|
||||
formComponent.form = new FormModel();
|
||||
expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should always enable save outcome for writeable form', () => {
|
||||
const formModel = new FormModel();
|
||||
|
||||
const field = new FormFieldModel(formModel, {
|
||||
type: 'text',
|
||||
value: null,
|
||||
required: true
|
||||
});
|
||||
|
||||
const containerModel = new ContainerModel(field);
|
||||
formModel.fields.push(containerModel);
|
||||
formComponent.form = formModel;
|
||||
formModel.onFormFieldChanged(field);
|
||||
|
||||
expect(formModel.isValid).toBeFalsy();
|
||||
|
||||
const outcome = new FormOutcomeModel(new FormModel(), {
|
||||
id: FormComponent.SAVE_OUTCOME_ID,
|
||||
name: FormOutcomeModel.SAVE_ACTION
|
||||
});
|
||||
|
||||
formComponent.readOnly = true;
|
||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should disable outcome buttons for invalid form', () => {
|
||||
const formModel = new FormModel();
|
||||
const field = new FormFieldModel(formModel, {
|
||||
type: 'text',
|
||||
value: null,
|
||||
required: true
|
||||
});
|
||||
|
||||
const containerModel = new ContainerModel(field);
|
||||
formModel.fields.push(containerModel);
|
||||
formComponent.form = formModel;
|
||||
formModel.onFormFieldChanged(field);
|
||||
|
||||
expect(formModel.isValid).toBeFalsy();
|
||||
|
||||
const outcome = new FormOutcomeModel(new FormModel(), {
|
||||
id: FormComponent.CUSTOM_OUTCOME_ID,
|
||||
name: 'Custom'
|
||||
});
|
||||
|
||||
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should disable complete outcome button when disableCompleteButton is true', () => {
|
||||
const formModel = new FormModel();
|
||||
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 FormModel();
|
||||
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) => {
|
||||
formService.executeOutcome.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
|
||||
const outcome = new FormOutcomeModel(new FormModel(), {
|
||||
id: FormComponent.CUSTOM_OUTCOME_ID,
|
||||
name: 'Custom'
|
||||
});
|
||||
|
||||
formComponent.form = new FormModel();
|
||||
formComponent.onOutcomeClicked(outcome);
|
||||
});
|
||||
|
||||
it('should refresh form values when data is changed', () => {
|
||||
formComponent.form = new FormModel(JSON.parse(JSON.stringify(fakeForm)));
|
||||
let formFields = formComponent.form.getFormFields();
|
||||
|
||||
let labelField = formFields.find((field) => field.id === 'label');
|
||||
let radioField = formFields.find((field) => field.id === 'radio');
|
||||
expect(labelField.value).toBe('empty');
|
||||
expect(radioField.value).toBeNull();
|
||||
|
||||
const formValues: any = {};
|
||||
formValues.label = {
|
||||
id: 'option_2',
|
||||
name: 'test2'
|
||||
};
|
||||
formValues.radio = { id: 'option_2', name: 'Option 2' };
|
||||
const change = new SimpleChange(null, formValues, false);
|
||||
formComponent.data = formValues;
|
||||
formComponent.ngOnChanges({ 'data': change });
|
||||
|
||||
formFields = formComponent.form.getFormFields();
|
||||
labelField = formFields.find((field) => field.id === 'label');
|
||||
radioField = formFields.find((field) => field.id === 'radio');
|
||||
expect(labelField.value).toBe('option_2');
|
||||
expect(radioField.value).toBe('option_2');
|
||||
});
|
||||
|
||||
it('should refresh radio buttons value when id is given to data', () => {
|
||||
formComponent.form = new FormModel(JSON.parse(JSON.stringify(fakeForm)));
|
||||
let formFields = formComponent.form.getFormFields();
|
||||
let radioFieldById = formFields.find((field) => field.id === 'radio');
|
||||
|
||||
const formValues: any = {};
|
||||
formValues.radio = 'option_3';
|
||||
const change = new SimpleChange(null, formValues, false);
|
||||
formComponent.data = formValues;
|
||||
formComponent.ngOnChanges({ 'data': change });
|
||||
|
||||
formFields = formComponent.form.getFormFields();
|
||||
radioFieldById = formFields.find((field) => field.id === 'radio');
|
||||
expect(radioFieldById.value).toBe('option_3');
|
||||
});
|
||||
});
|
@@ -1,586 +0,0 @@
|
||||
/*!
|
||||
* @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 */
|
||||
import {
|
||||
Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit,
|
||||
Output, SimpleChanges, ViewEncapsulation
|
||||
} 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 { 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';
|
||||
|
||||
/** Underlying form model instance. */
|
||||
@Input()
|
||||
form: FormModel;
|
||||
|
||||
/** Task id to fetch corresponding form and values. */
|
||||
@Input()
|
||||
taskId: string;
|
||||
|
||||
/** Content Services node ID for the form metadata. */
|
||||
@Input()
|
||||
nodeId: string;
|
||||
|
||||
/** The id of the form definition to load and display with custom values. */
|
||||
@Input()
|
||||
formId: number;
|
||||
|
||||
/** Name of the form definition to load and display with custom values. */
|
||||
@Input()
|
||||
formName: string;
|
||||
|
||||
/** Toggle saving of form metadata. */
|
||||
@Input()
|
||||
saveMetadata: boolean = false;
|
||||
|
||||
/** Custom form values map to be used with the rendered form. */
|
||||
@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<FormModel> = new EventEmitter<FormModel>();
|
||||
|
||||
/** Emitted when the form is submitted with the `Complete` outcome. */
|
||||
@Output()
|
||||
formCompleted: EventEmitter<FormModel> = new EventEmitter<FormModel>();
|
||||
|
||||
/** Emitted when form content is clicked. */
|
||||
@Output()
|
||||
formContentClicked: EventEmitter<ContentLinkModel> = new EventEmitter<ContentLinkModel>();
|
||||
|
||||
/** Emitted when the form is loaded or reloaded. */
|
||||
@Output()
|
||||
formLoaded: EventEmitter<FormModel> = new EventEmitter<FormModel>();
|
||||
|
||||
/** Emitted when form values are refreshed due to a data property change. */
|
||||
@Output()
|
||||
formDataRefreshed: EventEmitter<FormModel> = new EventEmitter<FormModel>();
|
||||
|
||||
/** Emitted when the supplied form values have a validation error.*/
|
||||
@Output()
|
||||
formError: EventEmitter<FormFieldModel[]> = new EventEmitter<FormFieldModel[]>();
|
||||
|
||||
/** Emitted when any outcome is executed. Default behaviour can be prevented
|
||||
* via `event.preventDefault()`.
|
||||
*/
|
||||
@Output()
|
||||
executeOutcome: EventEmitter<FormOutcomeEvent> = new EventEmitter<FormOutcomeEvent>();
|
||||
|
||||
/**
|
||||
* Emitted when any error occurs.
|
||||
*/
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscriptions.push(
|
||||
this.formService.formContentClicked.subscribe((content: ContentLinkModel) => {
|
||||
this.formContentClicked.emit(content);
|
||||
}),
|
||||
this.formService.validateForm.subscribe((validateFormEvent: ValidateFormEvent) => {
|
||||
if (validateFormEvent.errorsField.length > 0) {
|
||||
this.formError.next(validateFormEvent.errorsField);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
let taskId = changes['taskId'];
|
||||
if (taskId && taskId.currentValue) {
|
||||
this.getFormByTaskId(taskId.currentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
let formId = changes['formId'];
|
||||
if (formId && formId.currentValue) {
|
||||
this.getFormDefinitionByFormId(formId.currentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
let formName = changes['formName'];
|
||||
if (formName && formName.currentValue) {
|
||||
this.getFormDefinitionByFormName(formName.currentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
let nodeId = changes['nodeId'];
|
||||
if (nodeId && nodeId.currentValue) {
|
||||
this.loadFormForEcmNode(nodeId.currentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
let 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.
|
||||
*/
|
||||
onRefreshClicked() {
|
||||
this.loadForm();
|
||||
}
|
||||
|
||||
loadForm() {
|
||||
if (this.taskId) {
|
||||
this.getFormByTaskId(this.taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formId) {
|
||||
this.getFormDefinitionByFormId(this.formId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formName) {
|
||||
this.getFormDefinitionByFormName(this.formName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
findProcessVariablesByTaskId(taskId: string): Observable<any> {
|
||||
return this.formService.getTask(taskId).pipe(
|
||||
switchMap((task: any) => {
|
||||
if (this.isAProcessTask(task)) {
|
||||
return this.visibilityService.getTaskProcessVariable(taskId);
|
||||
} else {
|
||||
return of({});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
isAProcessTask(taskRepresentation) {
|
||||
return taskRepresentation.processDefinitionId && taskRepresentation.processDefinitionDeploymentId !== 'null';
|
||||
}
|
||||
|
||||
getFormByTaskId(taskId: string): Promise<FormModel> {
|
||||
return new Promise<FormModel>((resolve, reject) => {
|
||||
this.findProcessVariablesByTaskId(taskId).subscribe((processVariables) => {
|
||||
this.formService
|
||||
.getTaskForm(taskId)
|
||||
.subscribe(
|
||||
form => {
|
||||
const parsedForm = this.parseForm(form);
|
||||
this.visibilityService.refreshVisibility(parsedForm);
|
||||
parsedForm.validateForm();
|
||||
this.form = parsedForm;
|
||||
this.onFormLoaded(this.form);
|
||||
resolve(this.form);
|
||||
},
|
||||
error => {
|
||||
this.handleError(error);
|
||||
// reject(error);
|
||||
resolve(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getFormDefinitionByFormId(formId: number) {
|
||||
this.formService
|
||||
.getFormDefinitionById(formId)
|
||||
.subscribe(
|
||||
form => {
|
||||
this.formName = form.name;
|
||||
this.form = this.parseForm(form);
|
||||
this.visibilityService.refreshVisibility(this.form);
|
||||
this.form.validateForm();
|
||||
this.onFormLoaded(this.form);
|
||||
},
|
||||
(error) => {
|
||||
this.handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getFormDefinitionByFormName(formName: string) {
|
||||
this.formService
|
||||
.getFormDefinitionByName(formName)
|
||||
.subscribe(
|
||||
id => {
|
||||
this.formService.getFormDefinitionById(id).subscribe(
|
||||
form => {
|
||||
this.form = this.parseForm(form);
|
||||
this.visibilityService.refreshVisibility(this.form);
|
||||
this.form.validateForm();
|
||||
this.onFormLoaded(this.form);
|
||||
},
|
||||
(error) => {
|
||||
this.handleError(error);
|
||||
}
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this.handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
saveTaskForm() {
|
||||
if (this.form && this.form.taskId) {
|
||||
this.formService
|
||||
.saveTaskForm(this.form.taskId, this.form.values)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.onTaskSaved(this.form);
|
||||
this.storeFormAsMetadata();
|
||||
},
|
||||
error => this.onTaskSavedError(this.form, error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
completeTaskForm(outcome?: string) {
|
||||
if (this.form && this.form.taskId) {
|
||||
this.formService
|
||||
.completeTaskForm(this.form.taskId, this.form.values, outcome)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.onTaskCompleted(this.form);
|
||||
this.storeFormAsMetadata();
|
||||
},
|
||||
error => this.onTaskCompletedError(this.form, error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleError(err: any): any {
|
||||
this.error.emit(err);
|
||||
}
|
||||
|
||||
parseForm(json: any): FormModel {
|
||||
if (json) {
|
||||
let form = new FormModel(json, this.data, this.readOnly, this.formService);
|
||||
if (!json.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: FormModel): FormOutcomeModel[] {
|
||||
return [
|
||||
new FormOutcomeModel(form, { id: '$custom', 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);
|
||||
}
|
||||
|
||||
private loadFormForEcmNode(nodeId: string): void {
|
||||
this.nodeService.getNodeMetadata(nodeId).subscribe(data => {
|
||||
this.data = data.metadata;
|
||||
this.loadFormFromActiviti(data.nodeType);
|
||||
},
|
||||
this.handleError);
|
||||
}
|
||||
|
||||
loadFormFromActiviti(nodeType: string): any {
|
||||
this.formService.searchFrom(nodeType).subscribe(
|
||||
form => {
|
||||
if (!form) {
|
||||
this.formService.createFormFromANode(nodeType).subscribe(formMetadata => {
|
||||
this.loadFormFromFormId(formMetadata.id);
|
||||
});
|
||||
} else {
|
||||
this.loadFormFromFormId(form.id);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
this.handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private loadFormFromFormId(formId: number) {
|
||||
this.formId = formId;
|
||||
this.loadForm();
|
||||
}
|
||||
|
||||
private storeFormAsMetadata() {
|
||||
if (this.saveMetadata) {
|
||||
this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe(type => {
|
||||
this.nodeService.createNodeMetadata(type.nodeType || type.entry.prefixedName, EcmModelService.MODEL_NAMESPACE, this.form.values, this.path, this.nameNode);
|
||||
},
|
||||
(error) => {
|
||||
this.handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected onFormLoaded(form: FormModel) {
|
||||
this.formLoaded.emit(form);
|
||||
this.formService.formLoaded.next(new FormEvent(form));
|
||||
}
|
||||
|
||||
protected onFormDataRefreshed(form: FormModel) {
|
||||
this.formDataRefreshed.emit(form);
|
||||
this.formService.formDataRefreshed.next(new FormEvent(form));
|
||||
}
|
||||
|
||||
protected onTaskSaved(form: FormModel) {
|
||||
this.formSaved.emit(form);
|
||||
this.formService.taskSaved.next(new FormEvent(form));
|
||||
}
|
||||
|
||||
protected onTaskSavedError(form: FormModel, error: any) {
|
||||
this.handleError(error);
|
||||
this.formService.taskSavedError.next(new FormErrorEvent(form, error));
|
||||
}
|
||||
|
||||
protected onTaskCompleted(form: FormModel) {
|
||||
this.formCompleted.emit(form);
|
||||
this.formService.taskCompleted.next(new FormEvent(form));
|
||||
}
|
||||
|
||||
protected onTaskCompletedError(form: FormModel, error: any) {
|
||||
this.handleError(error);
|
||||
this.formService.taskCompletedError.next(new FormErrorEvent(form, error));
|
||||
}
|
||||
|
||||
protected onExecuteOutcome(outcome: FormOutcomeModel): boolean {
|
||||
let args = new FormOutcomeEvent(outcome);
|
||||
|
||||
this.formService.executeOutcome.next(args);
|
||||
if (args.defaultPrevented) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.executeOutcome.emit(args);
|
||||
if (args.defaultPrevented) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,243 +0,0 @@
|
||||
/*!
|
||||
* @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
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 { 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. */
|
||||
const SELECT_OPEN_ANIMATION = 200;
|
||||
|
||||
/** Duration of the select closing animation and the timeout interval for the backdrop. */
|
||||
const SELECT_CLOSE_ANIMATION = 500;
|
||||
|
||||
describe('FormComponent UI and visibility', () => {
|
||||
let component: FormComponent;
|
||||
let service: FormService;
|
||||
let fixture: ComponentFixture<FormComponent>;
|
||||
|
||||
function openSelect() {
|
||||
let trigger: HTMLElement;
|
||||
trigger = fixture.debugElement.query(By.css('[class="mat-select-trigger"]')).nativeElement;
|
||||
trigger.click();
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FormComponent);
|
||||
component = fixture.componentInstance;
|
||||
service = TestBed.get(FormService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should create instance of FormComponent', () => {
|
||||
expect(fixture.componentInstance instanceof FormComponent).toBe(true, 'should create FormComponent');
|
||||
});
|
||||
|
||||
describe('Validation icon', () => {
|
||||
|
||||
it('should display valid icon for valid form', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeDefined();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).not.toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeNull();
|
||||
});
|
||||
|
||||
it('should display invalid icon for valid form', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionRequiredField));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeDefined();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT display validation icon when [showValidationIcon] is false', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
component.showValidationIcon = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('form definition', () => {
|
||||
|
||||
it('should display two text fields form definition', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
|
||||
const firstNameEl = fixture.debugElement.query(By.css('#firstname'));
|
||||
expect(firstNameEl).not.toBeNull();
|
||||
expect(firstNameEl).toBeDefined();
|
||||
|
||||
const lastNameEl = fixture.debugElement.query(By.css('#lastname'));
|
||||
expect(lastNameEl).not.toBeNull();
|
||||
expect(lastNameEl).toBeDefined();
|
||||
});
|
||||
|
||||
it('should display dropdown field', fakeAsync(() => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionDropdownField));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
|
||||
openSelect();
|
||||
tick(SELECT_OPEN_ANIMATION);
|
||||
|
||||
const dropdown = fixture.debugElement.queryAll(By.css('#country'));
|
||||
expect(dropdown).toBeDefined();
|
||||
expect(dropdown).not.toBeNull();
|
||||
const options = fixture.debugElement.queryAll(By.css('mat-option'));
|
||||
const optOne = options[1];
|
||||
const optTwo = options[2];
|
||||
const optThree = options[3];
|
||||
|
||||
expect(optOne.nativeElement.innerText.trim()).toEqual('united kingdom');
|
||||
expect(optTwo.nativeElement.innerText.trim()).toEqual('italy');
|
||||
expect(optThree.nativeElement.innerText.trim()).toEqual('france');
|
||||
|
||||
optTwo.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
expect(dropdown[0].nativeElement.innerText.trim()).toEqual('italy');
|
||||
tick(SELECT_CLOSE_ANIMATION);
|
||||
}));
|
||||
|
||||
describe('Visibility conditions', () => {
|
||||
|
||||
it('should hide the field based on the next one', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnNextOne));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
|
||||
const firstEl = fixture.debugElement.query(By.css('#field-country-container'));
|
||||
expect(firstEl.nativeElement.hidden).toBeTruthy();
|
||||
|
||||
const secondEl = fixture.debugElement.query(By.css('#name'));
|
||||
expect(secondEl).not.toBeNull();
|
||||
expect(secondEl).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#field-name-container').hidden).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should hide the field based on the previous one', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnPreviousOne));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
|
||||
const firstEl = fixture.debugElement.query(By.css('#name'));
|
||||
expect(firstEl).not.toBeNull();
|
||||
expect(firstEl).toBeDefined();
|
||||
expect(fixture.nativeElement.querySelector('#field-name-container').hidden).toBeFalsy();
|
||||
|
||||
const secondEl = fixture.debugElement.query(By.css('#field-country-container'));
|
||||
expect(secondEl.nativeElement.hidden).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show the hidden field when the visibility condition change to true', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnNextOne));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
|
||||
let firstEl = fixture.debugElement.query(By.css('#field-country-container'));
|
||||
expect(firstEl.nativeElement.hidden).toBeTruthy();
|
||||
|
||||
const secondEl = fixture.debugElement.query(By.css('#field-name-container'));
|
||||
expect(secondEl.nativeElement.hidden).toBeFalsy();
|
||||
|
||||
const inputElement = fixture.nativeElement.querySelector('#name');
|
||||
inputElement.value = 'italy';
|
||||
inputElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
||||
firstEl = fixture.debugElement.query(By.css('#field-country-container'));
|
||||
expect(firstEl.nativeElement.hidden).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Readonly Form', () => {
|
||||
it('should display two text fields readonly', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(of(formReadonlyTwoTextFields));
|
||||
|
||||
const change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
|
||||
const firstNameEl = fixture.debugElement.query(By.css('#firstname'));
|
||||
expect(firstNameEl).not.toBeNull();
|
||||
expect(firstNameEl).toBeDefined();
|
||||
expect(firstNameEl.nativeElement.value).toEqual('fakeFirstName');
|
||||
|
||||
const lastNameEl = fixture.debugElement.query(By.css('#lastname'));
|
||||
expect(lastNameEl).not.toBeNull();
|
||||
expect(lastNameEl).toBeDefined();
|
||||
expect(lastNameEl.nativeElement.value).toEqual('fakeLastName');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,40 +0,0 @@
|
||||
<div class="adf-start-form-container" *ngIf="hasForm()">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<h2 *ngIf="isTitleEnabled()" class="mdl-card__title-text">{{form.taskName}}</h2>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div *ngIf="form.hasTabs()">
|
||||
<tabs-widget [tabs]="form.tabs" (formTabChanged)="checkVisibility($event);"></tabs-widget>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!form.hasTabs() && form.hasFields()">
|
||||
<div *ngFor="let field of form.fields">
|
||||
<adf-form-field [field]="field.field"></adf-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<mat-card-content class="adf-start-form-actions" *ngIf="showOutcomeButtons && form.hasOutcomes()"
|
||||
#outcomesContainer>
|
||||
<ng-content select="[adf-form-custom-button], [form-custom-button]"></ng-content>
|
||||
|
||||
<button *ngFor="let outcome of form.outcomes"
|
||||
mat-button
|
||||
[attr.data-automation-id]="'adf-form-' + outcome.name | lowercase"
|
||||
[disabled]="!isOutcomeButtonEnabled(outcome)"
|
||||
[class.mdl-button--colored]="!outcome.isSystem"
|
||||
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
|
||||
(click)="onOutcomeClicked(outcome)">
|
||||
{{ outcome.name | uppercase | translate | uppercase }}
|
||||
</button>
|
||||
</mat-card-content>
|
||||
<mat-card-actions *ngIf="showRefreshButton">
|
||||
<button mat-button
|
||||
(click)="onRefreshClicked()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
@@ -1,441 +0,0 @@
|
||||
/*!
|
||||
* @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 { 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 { 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';
|
||||
|
||||
describe('StartFormComponent', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let component: StartFormComponent;
|
||||
let fixture: ComponentFixture<StartFormComponent>;
|
||||
let getStartFormSpy: jasmine.Spy;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
|
||||
const exampleId1 = 'my:process1';
|
||||
const exampleId2 = 'my:process2';
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StartFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
formService = TestBed.get(FormService);
|
||||
visibilityService = TestBed.get(WidgetVisibilityService);
|
||||
|
||||
getStartFormSpy = spyOn(formService, 'getStartFormDefinition').and.returnValue(of({
|
||||
processDefinitionName: 'my:process'
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should load start form on change if processDefinitionId defined', () => {
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
expect(formService.getStartFormDefinition).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should load start form when processDefinitionId changed', () => {
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
expect(formService.getStartFormDefinition).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should check visibility when the start form is loaded', () => {
|
||||
spyOn(visibilityService, 'refreshVisibility');
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
expect(formService.getStartFormDefinition).toHaveBeenCalled();
|
||||
expect(visibilityService.refreshVisibility).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not load start form when changes notified but no change to processDefinitionId', () => {
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.ngOnChanges({ otherProp: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
expect(formService.getStartFormDefinition).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should consume errors encountered when loading start form', () => {
|
||||
getStartFormSpy.and.returnValue(throwError({}));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.ngOnInit();
|
||||
});
|
||||
|
||||
it('should show outcome buttons by default', () => {
|
||||
getStartFormSpy.and.returnValue(of({
|
||||
id: '1',
|
||||
processDefinitionName: 'my:process',
|
||||
outcomes: [{
|
||||
id: 'approve',
|
||||
name: 'Approve'
|
||||
}]
|
||||
}));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.ngOnInit();
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
expect(component.outcomesContainer).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show outcome buttons if showOutcomeButtons is true', () => {
|
||||
getStartFormSpy.and.returnValue(of({
|
||||
id: '1',
|
||||
processDefinitionName: 'my:process',
|
||||
outcomes: [{
|
||||
id: 'approve',
|
||||
name: 'Approve'
|
||||
}]
|
||||
}));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
expect(component.outcomesContainer).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fetch start form details by processDefinitionId ', () => {
|
||||
getStartFormSpy.and.returnValue(of(startMockForm));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
expect(component.outcomesContainer).toBeTruthy();
|
||||
expect(getStartFormSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Display widgets', () => {
|
||||
|
||||
it('should be able to display a textWidget from a process definition', () => {
|
||||
getStartFormSpy.and.returnValue(of(startFormTextDefinitionMock));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find((field) => field.id === 'mocktext');
|
||||
const textWidget = fixture.debugElement.nativeElement.querySelector('text-widget');
|
||||
const textWidgetLabel = fixture.debugElement.nativeElement.querySelector('.adf-label');
|
||||
expect(labelField.type).toBe('text');
|
||||
expect(textWidget).toBeDefined();
|
||||
expect(textWidgetLabel.innerText).toBe('mockText');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to display a radioButtonWidget from a process definition', () => {
|
||||
getStartFormSpy.and.returnValue(of(startFormRadioButtonWidgetMock));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find((field) => field.id === 'radio-but');
|
||||
const radioButtonWidget = fixture.debugElement.nativeElement.querySelector('radio-buttons-widget');
|
||||
const radioButtonWidgetLabel = fixture.debugElement.nativeElement.querySelector('.adf-input');
|
||||
expect(labelField.type).toBe('radio-buttons');
|
||||
expect(radioButtonWidget).toBeDefined();
|
||||
expect(radioButtonWidgetLabel.innerText).toBe('radio-buttons');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to display a amountWidget from a process definition', () => {
|
||||
getStartFormSpy.and.returnValue(of(startFormAmountWidgetMock));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find((field) => field.id === 'amount');
|
||||
const amountWidget = fixture.debugElement.nativeElement.querySelector('amount-widget');
|
||||
const amountWidgetLabel = fixture.debugElement.nativeElement.querySelector('.adf-input');
|
||||
expect(labelField.type).toBe('amount');
|
||||
expect(amountWidget).toBeDefined();
|
||||
expect(amountWidgetLabel.innerText).toBe('amount');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to display a numberWidget from a process definition', () => {
|
||||
getStartFormSpy.and.returnValue(of(startFormNumberWidgetMock));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find((field) => field.id === 'number');
|
||||
const numberWidget = fixture.debugElement.nativeElement.querySelector('number-widget');
|
||||
expect(labelField.type).toBe('integer');
|
||||
expect(numberWidget).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to display a dropDown Widget from a process definition', () => {
|
||||
getStartFormSpy.and.returnValue(of(startFormDropdownDefinitionMock));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find((field) => field.id === 'mockTypeDropDown');
|
||||
const dropDownWidget = fixture.debugElement.nativeElement.querySelector('dropdown-widget');
|
||||
const selectElement = fixture.debugElement.nativeElement.querySelector('.adf-dropdown-widget>mat-select .mat-select-trigger');
|
||||
selectElement.click();
|
||||
expect(selectElement).toBeDefined();
|
||||
expect(dropDownWidget).toBeDefined();
|
||||
expect(selectElement.innerText).toBe('Choose one...');
|
||||
expect(labelField.type).toBe('dropdown');
|
||||
expect(labelField.options[0].name).toBe('Chooseone...');
|
||||
expect(labelField.options[1].name).toBe('Option-1');
|
||||
expect(labelField.options[2].name).toBe('Option-2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to display a date Widget from a process definition', () => {
|
||||
getStartFormSpy.and.returnValue(of(startFormDateWidgetMock));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find((field) => field.id === 'date');
|
||||
const dateWidget = fixture.debugElement.nativeElement.querySelector('dropdown-widget');
|
||||
const dateLabelElement = fixture.debugElement.nativeElement.querySelector('#data-widget .mat-form-field-infix> .adf-label');
|
||||
expect(dateWidget).toBeDefined();
|
||||
expect(labelField.type).toBe('date');
|
||||
expect(dateLabelElement.innerText).toBe('date (D-M-YYYY)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch and define form fields with proper type', () => {
|
||||
getStartFormSpy.and.returnValue(of(startMockForm));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
const formFields = component.form.getFormFields();
|
||||
|
||||
const labelField = formFields.find((field) => field.id === 'billdate');
|
||||
expect(labelField.type).toBe('date');
|
||||
|
||||
const formFields1 = component.form.getFormFields();
|
||||
const labelField1 = formFields1.find((field) => field.id === 'claimtype');
|
||||
expect(labelField1.type).toBe('dropdown');
|
||||
});
|
||||
|
||||
it('should show dropdown options', () => {
|
||||
getStartFormSpy.and.returnValue(of(startMockForm));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find((field) => field.id === 'claimtype');
|
||||
expect(labelField.type).toBe('dropdown');
|
||||
expect(labelField.options[0].name).toBe('Chooseone...');
|
||||
expect(labelField.options[1].name).toBe('Cashless');
|
||||
expect(labelField.options[2].name).toBe('Reimbursement');
|
||||
});
|
||||
});
|
||||
|
||||
it('should display start form with fields ', async(() => {
|
||||
getStartFormSpy.and.returnValue(of(startMockForm));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const formFieldsWidget = fixture.debugElement.nativeElement.querySelector('form-field');
|
||||
const inputElement = fixture.debugElement.nativeElement.querySelector('.adf-input');
|
||||
const inputLabelElement = fixture.debugElement.nativeElement.querySelector('.mat-form-field-infix > .adf-label');
|
||||
const dateElement = fixture.debugElement.nativeElement.querySelector('#billdate');
|
||||
const dateLabelElement = fixture.debugElement.nativeElement.querySelector('#data-widget .mat-form-field-infix> .adf-label');
|
||||
const selectElement = fixture.debugElement.nativeElement.querySelector('#claimtype');
|
||||
const selectLabelElement = fixture.debugElement.nativeElement.querySelector('.adf-dropdown-widget > .adf-label');
|
||||
expect(formFieldsWidget).toBeDefined();
|
||||
expect(inputElement).toBeDefined();
|
||||
expect(dateElement).toBeDefined();
|
||||
expect(selectElement).toBeDefined();
|
||||
expect(inputLabelElement.innerText).toBe('ClientName*');
|
||||
expect(dateLabelElement.innerText).toBe('BillDate (D-M-YYYY)');
|
||||
expect(selectLabelElement.innerText).toBe('ClaimType');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should refresh start form on click of refresh button ', async(() => {
|
||||
getStartFormSpy.and.returnValue(of(startMockForm));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.showRefreshButton = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const refreshElement = fixture.debugElement.nativeElement.querySelector('.mat-card-actions>button');
|
||||
refreshElement.click();
|
||||
fixture.detectChanges();
|
||||
/* cspell:disable-next-line */
|
||||
const selectElement = fixture.debugElement.nativeElement.querySelector('#claimtype');
|
||||
const selectLabelElement = fixture.debugElement.nativeElement.querySelector('.adf-dropdown-widget > .adf-label');
|
||||
expect(refreshElement).toBeDefined();
|
||||
expect(selectElement).toBeDefined();
|
||||
expect(selectLabelElement.innerText).toBe('ClaimType');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should define custom-tabs ', async(() => {
|
||||
getStartFormSpy.and.returnValue(of(startMockFormWithTab));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.showRefreshButton = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const formTabs = component.form.tabs;
|
||||
const tabField1 = formTabs.find((tab) => tab.id === 'form1');
|
||||
const tabField2 = formTabs.find((tab) => tab.id === 'form2');
|
||||
const tabsWidgetElement = fixture.debugElement.nativeElement.querySelector('tabs-widget');
|
||||
expect(tabField1.name).toBe('Tab 1');
|
||||
expect(tabField2.name).toBe('Tab 2');
|
||||
expect(tabsWidgetElement).toBeDefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should define title and [custom-action-buttons]', async(() => {
|
||||
getStartFormSpy.and.returnValue(of(startMockFormWithTab));
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.showOutcomeButtons = true;
|
||||
component.showRefreshButton = true;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const titleIcon = fixture.debugElement.nativeElement.querySelector('mat-card-title>mat-icon');
|
||||
const titleElement = fixture.debugElement.nativeElement.querySelector('mat-card-title>h2');
|
||||
const actionButtons = fixture.debugElement.nativeElement.querySelectorAll('.mat-button');
|
||||
expect(titleIcon).toBeDefined();
|
||||
expect(titleElement).toBeDefined();
|
||||
expect(actionButtons.length).toBe(4);
|
||||
expect(actionButtons[0].innerText).toBe('Save');
|
||||
expect(actionButtons[0].disabled).toBeFalsy();
|
||||
expect(actionButtons[1].innerText).toBe('Approve');
|
||||
expect(actionButtons[1].disabled).toBeTruthy();
|
||||
expect(actionButtons[2].innerText).toBe('Complete');
|
||||
expect(actionButtons[2].disabled).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('OutCome Actions', () => {
|
||||
|
||||
it('should not enable outcome button when model missing', () => {
|
||||
expect(component.isOutcomeButtonVisible(null, false)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should enable custom outcome buttons', () => {
|
||||
const formModel = new FormModel();
|
||||
component.form = formModel;
|
||||
const outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' });
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow controlling [complete] button visibility', () => {
|
||||
const formModel = new FormModel();
|
||||
component.form = formModel;
|
||||
const outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
|
||||
|
||||
component.showSaveButton = true;
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeTruthy();
|
||||
|
||||
component.showSaveButton = false;
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show only [complete] button with readOnly form ', () => {
|
||||
const formModel = new FormModel();
|
||||
formModel.readOnly = true;
|
||||
component.form = formModel;
|
||||
const outcome = new FormOutcomeModel(formModel, { id: '$complete', name: FormOutcomeModel.COMPLETE_ACTION });
|
||||
|
||||
component.showCompleteButton = true;
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not show [save] button with readOnly form ', () => {
|
||||
const formModel = new FormModel();
|
||||
formModel.readOnly = true;
|
||||
component.form = formModel;
|
||||
const outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
|
||||
|
||||
component.showSaveButton = true;
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show [custom-outcome] button with readOnly form and selected custom-outcome', () => {
|
||||
const formModel = new FormModel({ selectedOutcome: 'custom-outcome' });
|
||||
formModel.readOnly = true;
|
||||
component.form = formModel;
|
||||
const outcome = new FormOutcomeModel(formModel, { id: '$customoutome', name: 'custom-outcome' });
|
||||
|
||||
component.showCompleteButton = true;
|
||||
component.showSaveButton = true;
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeTruthy();
|
||||
|
||||
const outcome1 = new FormOutcomeModel(formModel, { id: '$customoutome2', name: 'custom-outcome2' });
|
||||
expect(component.isOutcomeButtonVisible(outcome1, component.form.readOnly)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should allow controlling [save] button visibility', () => {
|
||||
const formModel = new FormModel();
|
||||
formModel.readOnly = false;
|
||||
component.form = formModel;
|
||||
const outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION });
|
||||
|
||||
component.showCompleteButton = true;
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeTruthy();
|
||||
|
||||
component.showCompleteButton = false;
|
||||
expect(component.isOutcomeButtonVisible(outcome, component.form.readOnly)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,185 +0,0 @@
|
||||
/*!
|
||||
* @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,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-start-form',
|
||||
templateUrl: './start-form.component.html',
|
||||
styleUrls: ['./form.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class StartFormComponent extends FormComponent implements OnChanges, OnInit, OnDestroy {
|
||||
|
||||
/** Definition ID of the process to start. */
|
||||
@Input()
|
||||
processDefinitionId: string;
|
||||
|
||||
/** Process ID of the process to start. */
|
||||
@Input()
|
||||
processId: string;
|
||||
|
||||
/** Should form outcome buttons be shown? */
|
||||
@Input()
|
||||
showOutcomeButtons: boolean = true;
|
||||
|
||||
/** Should the refresh button be shown? */
|
||||
@Input()
|
||||
showRefreshButton: boolean = true;
|
||||
|
||||
/** Is the form read-only (ie, can't be edited)? */
|
||||
@Input()
|
||||
readOnlyForm: boolean = false;
|
||||
|
||||
/** Emitted when the user clicks one of the outcome buttons that completes the form. */
|
||||
@Output()
|
||||
outcomeClick: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
/** Emitted when a field of the form is clicked. */
|
||||
@Output()
|
||||
formContentClicked: EventEmitter<ContentLinkModel> = new EventEmitter<ContentLinkModel>();
|
||||
|
||||
@ViewChild('outcomesContainer', {})
|
||||
outcomesContainer: ElementRef = null;
|
||||
|
||||
constructor(formService: FormService,
|
||||
visibilityService: WidgetVisibilityService) {
|
||||
super(formService, visibilityService, null, null);
|
||||
this.showTitle = false;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscriptions.push(
|
||||
this.formService.formContentClicked.subscribe((content) => {
|
||||
this.formContentClicked.emit(content);
|
||||
}),
|
||||
this.formService.validateForm.subscribe((validateFormEvent: ValidateFormEvent) => {
|
||||
if (validateFormEvent.errorsField.length > 0) {
|
||||
this.formError.next(validateFormEvent.errorsField);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const processDefinitionId = changes['processDefinitionId'];
|
||||
if (processDefinitionId && processDefinitionId.currentValue) {
|
||||
this.visibilityService.cleanProcessVariable();
|
||||
this.getStartFormDefinition(processDefinitionId.currentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
const processId = changes['processId'];
|
||||
if (processId && processId.currentValue) {
|
||||
this.visibilityService.cleanProcessVariable();
|
||||
this.loadStartForm(processId.currentValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loadStartForm(processId: string) {
|
||||
this.formService.getProcessInstance(processId)
|
||||
.subscribe((instance: any) => {
|
||||
this.formService
|
||||
.getStartFormInstance(processId)
|
||||
.subscribe(
|
||||
(form) => {
|
||||
this.formName = form.name;
|
||||
if (instance.variables) {
|
||||
form.processVariables = instance.variables;
|
||||
}
|
||||
this.form = this.parseForm(form);
|
||||
this.visibilityService.refreshVisibility(this.form);
|
||||
this.form.validateForm();
|
||||
this.form.readOnly = this.readOnlyForm;
|
||||
this.onFormLoaded(this.form);
|
||||
},
|
||||
(error) => this.handleError(error)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getStartFormDefinition(processId: string) {
|
||||
this.formService
|
||||
.getStartFormDefinition(processId)
|
||||
.subscribe(
|
||||
(form) => {
|
||||
this.formName = form.processDefinitionName;
|
||||
this.form = this.parseForm(form);
|
||||
this.visibilityService.refreshVisibility(this.form);
|
||||
this.form.validateForm();
|
||||
this.form.readOnly = this.readOnlyForm;
|
||||
this.onFormLoaded(this.form);
|
||||
},
|
||||
(error) => this.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
isOutcomeButtonVisible(outcome: FormOutcomeModel, isFormReadOnly: boolean): boolean {
|
||||
if (outcome && outcome.isSystem && (outcome.name === FormOutcomeModel.SAVE_ACTION ||
|
||||
outcome.name === FormOutcomeModel.COMPLETE_ACTION)) {
|
||||
return false;
|
||||
} else if (outcome && outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
|
||||
return true;
|
||||
}
|
||||
return super.isOutcomeButtonVisible(outcome, isFormReadOnly);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
saveTaskForm() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/** @override */
|
||||
onRefreshClicked() {
|
||||
if (this.processDefinitionId) {
|
||||
this.visibilityService.cleanProcessVariable();
|
||||
this.getStartFormDefinition(this.processDefinitionId);
|
||||
} else if (this.processId) {
|
||||
this.visibilityService.cleanProcessVariable();
|
||||
this.loadStartForm(this.processId);
|
||||
}
|
||||
}
|
||||
|
||||
completeTaskForm(outcome?: string) {
|
||||
this.outcomeClick.emit(outcome);
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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 = <ContainerModel> field;
|
||||
formFieldModel.push(container.field);
|
||||
|
||||
container.field.columns.forEach((column) => {
|
||||
formFieldModel.push(...column.fields);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return formFieldModel;
|
||||
}
|
||||
|
||||
markAsInvalid() {
|
||||
this._isValid = false;
|
||||
}
|
||||
|
@@ -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 {
|
||||
}
|
@@ -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';
|
||||
|
@@ -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
|
||||
})
|
||||
|
Reference in New Issue
Block a user