mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACTIVITI-3720] cloud form support for form variable mapping (#5044)
* universal form model * parse variables correctly * turn group model into interface * remove console.log * interface instead of class * update form id type * improved form variable parsing * improved variable conversion * fix cloud tests * fix typings and code bugs
This commit is contained in:
committed by
Eugenio Romano
parent
53dc5f0b91
commit
2360ccc6d5
@@ -15,8 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormBaseModel } from './form-base.model';
|
||||
import { FormOutcomeModel, FormFieldValidator, FormFieldModel, FormOutcomeEvent } from './widgets';
|
||||
import { FormOutcomeModel, FormFieldValidator, FormFieldModel, FormOutcomeEvent, FormModel } from './widgets';
|
||||
import { EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
export abstract class FormBaseComponent {
|
||||
@@ -92,7 +91,7 @@ export abstract class FormBaseComponent {
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
form: FormBaseModel;
|
||||
form: FormModel;
|
||||
|
||||
getParsedFormDefinition(): FormBaseComponent {
|
||||
return this;
|
||||
@@ -208,7 +207,7 @@ export abstract class FormBaseComponent {
|
||||
|
||||
abstract completeTaskForm(outcome?: string);
|
||||
|
||||
protected abstract onTaskSaved(form: FormBaseModel);
|
||||
protected abstract onTaskSaved(form: FormModel);
|
||||
|
||||
protected abstract storeFormAsMetadata();
|
||||
|
||||
|
@@ -1,88 +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 { 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;
|
||||
|
||||
values: FormValues = {};
|
||||
tabs: TabModel[] = [];
|
||||
fields: FormWidgetModel[] = [];
|
||||
outcomes: FormOutcomeModel[] = [];
|
||||
|
||||
className: string;
|
||||
readOnly: boolean = false;
|
||||
taskName;
|
||||
|
||||
isValid: boolean = true;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
markAsInvalid() {
|
||||
this.isValid = false;
|
||||
}
|
||||
|
||||
abstract validateForm();
|
||||
abstract validateField(field: FormFieldModel);
|
||||
abstract onFormFieldChanged(field: FormFieldModel);
|
||||
}
|
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, Input } from '@angular/core';
|
||||
import { FormBaseModel } from './form-base.model';
|
||||
import { FormModel } from './widgets/core/form.model';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-form-renderer',
|
||||
@@ -31,7 +31,7 @@ export class FormRendererComponent {
|
||||
showDebugButton: boolean = false;
|
||||
|
||||
@Input()
|
||||
formDefinition: FormBaseModel;
|
||||
formDefinition: FormModel;
|
||||
|
||||
debugMode: boolean;
|
||||
|
||||
|
@@ -15,71 +15,98 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
import { FormFieldEvent } from './../../../events/form-field.event';
|
||||
import { ValidateFormFieldEvent } from './../../../events/validate-form-field.event';
|
||||
import { ValidateFormEvent } from './../../../events/validate-form.event';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { ContainerModel } from './container.model';
|
||||
import { FormFieldTemplates } from './form-field-templates';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormOutcomeModel } from './form-outcome.model';
|
||||
import { FormValues } from './form-values';
|
||||
import { FormWidgetModel, FormWidgetModelCache } from './form-widget.model';
|
||||
import { TabModel } from './tab.model';
|
||||
|
||||
import {
|
||||
FORM_FIELD_VALIDATORS,
|
||||
FormFieldValidator
|
||||
} from './form-field-validator';
|
||||
import { FormBaseModel } from '../../form-base.model';
|
||||
import { FormVariableModel } from './form-variable.model';
|
||||
import { ProcessVariableModel } from './process-variable.model';
|
||||
import { FormOutcomeModel } from './form-outcome.model';
|
||||
import { FormFieldValidator, FORM_FIELD_VALIDATORS } from './form-field-validator';
|
||||
import { FormFieldTemplates } from './form-field-templates';
|
||||
|
||||
export class FormModel extends FormBaseModel {
|
||||
export interface FormRepresentationModel {
|
||||
[key: string]: any;
|
||||
|
||||
readonly id: number;
|
||||
id?: string | number;
|
||||
name?: string;
|
||||
taskId?: string;
|
||||
taskName?: string;
|
||||
processDefinitionId?: string;
|
||||
customFieldTemplates?: {
|
||||
[key: string]: string
|
||||
};
|
||||
selectedOutcome?: string;
|
||||
fields?: any[];
|
||||
tabs?: any[];
|
||||
outcomes?: any[];
|
||||
formDefinition?: {
|
||||
fields?: any[];
|
||||
};
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
readonly id: string | number;
|
||||
readonly name: string;
|
||||
readonly taskId: string;
|
||||
readonly taskName: string = FormModel.UNSET_TASK_NAME;
|
||||
processDefinitionId: string;
|
||||
|
||||
customFieldTemplates: FormFieldTemplates = {};
|
||||
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
|
||||
readonly taskName = FormModel.UNSET_TASK_NAME;
|
||||
readonly processDefinitionId: string;
|
||||
readonly selectedOutcome: string;
|
||||
|
||||
json: FormRepresentationModel;
|
||||
nodeId: string;
|
||||
contentHost: string;
|
||||
values: FormValues = {};
|
||||
tabs: TabModel[] = [];
|
||||
fields: FormWidgetModel[] = [];
|
||||
outcomes: FormOutcomeModel[] = [];
|
||||
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
|
||||
customFieldTemplates: FormFieldTemplates = {};
|
||||
|
||||
className: string;
|
||||
readOnly = false;
|
||||
isValid = true;
|
||||
processVariables: ProcessVariableModel[] = [];
|
||||
variables: FormVariableModel[] = [];
|
||||
|
||||
constructor(formRepresentationJSON?: any, formValues?: FormValues, readOnly: boolean = false, protected formService?: FormService) {
|
||||
super();
|
||||
constructor(json?: FormRepresentationModel, formValues?: FormValues, readOnly: boolean = false, protected formService?: FormService) {
|
||||
this.readOnly = readOnly;
|
||||
this.json = json;
|
||||
|
||||
if (formRepresentationJSON) {
|
||||
this.json = formRepresentationJSON;
|
||||
|
||||
this.id = formRepresentationJSON.id;
|
||||
this.name = formRepresentationJSON.name;
|
||||
this.taskId = formRepresentationJSON.taskId;
|
||||
this.taskName = formRepresentationJSON.taskName || formRepresentationJSON.name || FormModel.UNSET_TASK_NAME;
|
||||
this.processDefinitionId = formRepresentationJSON.processDefinitionId;
|
||||
this.customFieldTemplates = formRepresentationJSON.customFieldTemplates || {};
|
||||
this.selectedOutcome = formRepresentationJSON.selectedOutcome || {};
|
||||
this.className = formRepresentationJSON.className || '';
|
||||
this.variables = formRepresentationJSON.variables || [];
|
||||
this.processVariables = formRepresentationJSON.processVariables || [];
|
||||
if (json) {
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.taskId = json.taskId;
|
||||
this.taskName = json.taskName || json.name || FormModel.UNSET_TASK_NAME;
|
||||
this.processDefinitionId = json.processDefinitionId;
|
||||
this.customFieldTemplates = json.customFieldTemplates || {};
|
||||
this.selectedOutcome = json.selectedOutcome;
|
||||
this.className = json.className || '';
|
||||
this.variables = json.variables || [];
|
||||
this.processVariables = json.processVariables || [];
|
||||
|
||||
const tabCache: FormWidgetModelCache<TabModel> = {};
|
||||
|
||||
this.tabs = (formRepresentationJSON.tabs || []).map((t) => {
|
||||
const model = new TabModel(this, t);
|
||||
this.tabs = (json.tabs || []).map((tabJson) => {
|
||||
const model = new TabModel(this, tabJson);
|
||||
tabCache[model.id] = model;
|
||||
return model;
|
||||
});
|
||||
|
||||
this.fields = this.parseRootFields(formRepresentationJSON);
|
||||
this.fields = this.parseRootFields(json);
|
||||
|
||||
if (formValues) {
|
||||
this.loadData(formValues);
|
||||
@@ -95,29 +122,7 @@ export class FormModel extends FormBaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (formRepresentationJSON.fields) {
|
||||
const saveOutcome = new FormOutcomeModel(this, {
|
||||
id: FormModel.SAVE_OUTCOME,
|
||||
name: 'SAVE',
|
||||
isSystem: true
|
||||
});
|
||||
const completeOutcome = new FormOutcomeModel(this, {
|
||||
id: FormModel.COMPLETE_OUTCOME,
|
||||
name: 'COMPLETE',
|
||||
isSystem: true
|
||||
});
|
||||
const startProcessOutcome = new FormOutcomeModel(this, {
|
||||
id: FormModel.START_PROCESS_OUTCOME,
|
||||
name: 'START PROCESS',
|
||||
isSystem: true
|
||||
});
|
||||
|
||||
const customOutcomes = (formRepresentationJSON.outcomes || []).map((obj) => new FormOutcomeModel(this, obj));
|
||||
|
||||
this.outcomes = [saveOutcome].concat(
|
||||
customOutcomes.length > 0 ? customOutcomes : [completeOutcome, startProcessOutcome]
|
||||
);
|
||||
}
|
||||
this.parseOutcomes();
|
||||
}
|
||||
|
||||
this.validateForm();
|
||||
@@ -125,6 +130,7 @@ export class FormModel extends FormBaseModel {
|
||||
|
||||
onFormFieldChanged(field: FormFieldModel) {
|
||||
this.validateField(field);
|
||||
|
||||
if (this.formService) {
|
||||
this.formService.formFieldValueChanged.next(new FormFieldEvent(this, field));
|
||||
}
|
||||
@@ -223,8 +229,10 @@ export class FormModel extends FormBaseModel {
|
||||
// Typically used when form definition and form data coming from different sources
|
||||
private loadData(formValues: FormValues) {
|
||||
for (const field of this.getFormFields()) {
|
||||
if (formValues[field.id]) {
|
||||
field.json.value = formValues[field.id];
|
||||
const variableId = `variables.${field.name}`;
|
||||
|
||||
if (formValues[variableId] || formValues[field.id]) {
|
||||
field.json.value = formValues[variableId] || formValues[field.id];
|
||||
field.value = field.parseValue(field.json);
|
||||
}
|
||||
}
|
||||
@@ -253,15 +261,8 @@ export class FormModel extends FormBaseModel {
|
||||
getFormVariableValue(identifier: string): any {
|
||||
const variable = this.getFormVariable(identifier);
|
||||
|
||||
if (variable) {
|
||||
switch (variable.type) {
|
||||
case 'date':
|
||||
return `${variable.value}T00:00:00.000Z`;
|
||||
case 'boolean':
|
||||
return JSON.parse(variable.value);
|
||||
default:
|
||||
return variable.value;
|
||||
}
|
||||
if (variable && variable.hasOwnProperty('value')) {
|
||||
return this.parseValue(variable.type, variable.value);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -280,15 +281,98 @@ export class FormModel extends FormBaseModel {
|
||||
);
|
||||
|
||||
if (variable) {
|
||||
switch (variable.type) {
|
||||
case 'boolean':
|
||||
return JSON.parse(variable.value);
|
||||
default:
|
||||
return variable.value;
|
||||
}
|
||||
return this.parseValue(variable.type, variable.value);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected parseValue(type: string, value: any): any {
|
||||
if (type && value) {
|
||||
switch (type) {
|
||||
case 'date':
|
||||
return value
|
||||
? `${value}T00:00:00.000Z`
|
||||
: undefined;
|
||||
case 'boolean':
|
||||
return typeof value === 'string'
|
||||
? JSON.parse(value)
|
||||
: value;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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(): void {
|
||||
this.isValid = false;
|
||||
}
|
||||
|
||||
protected parseOutcomes() {
|
||||
if (this.json.fields) {
|
||||
const saveOutcome = new FormOutcomeModel(<any> this, {
|
||||
id: FormModel.SAVE_OUTCOME,
|
||||
name: 'SAVE',
|
||||
isSystem: true
|
||||
});
|
||||
const completeOutcome = new FormOutcomeModel(<any> this, {
|
||||
id: FormModel.COMPLETE_OUTCOME,
|
||||
name: 'COMPLETE',
|
||||
isSystem: true
|
||||
});
|
||||
const startProcessOutcome = new FormOutcomeModel(<any> this, {
|
||||
id: FormModel.START_PROCESS_OUTCOME,
|
||||
name: 'START PROCESS',
|
||||
isSystem: true
|
||||
});
|
||||
|
||||
const customOutcomes = (this.json.outcomes || []).map(
|
||||
(obj) => new FormOutcomeModel(<any> this, obj)
|
||||
);
|
||||
|
||||
this.outcomes = [saveOutcome].concat(
|
||||
customOutcomes.length > 0
|
||||
? customOutcomes
|
||||
: [completeOutcome, startProcessOutcome]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,24 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* tslint:disable:component-selector */
|
||||
|
||||
export class GroupModel {
|
||||
|
||||
externalId: string;
|
||||
groups: any;
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
|
||||
constructor(json?: any) {
|
||||
if (json) {
|
||||
this.externalId = json.externalId;
|
||||
this.groups = json.groups;
|
||||
this.id = json.id;
|
||||
this.name = json.name;
|
||||
this.status = json.status;
|
||||
}
|
||||
}
|
||||
|
||||
export interface GroupModel {
|
||||
externalId?: string;
|
||||
groups?: any;
|
||||
id?: string;
|
||||
name?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ describe('FunctionalGroupWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should setup text from underlying field on init', () => {
|
||||
const group = new GroupModel({ name: 'group-1'});
|
||||
const group: GroupModel = { name: 'group-1'};
|
||||
widget.field.value = group;
|
||||
|
||||
spyOn(formService, 'getWorkflowGroups').and.returnValue(
|
||||
@@ -81,7 +81,7 @@ describe('FunctionalGroupWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should update values on item click', () => {
|
||||
const item = new GroupModel({ name: 'group-1' });
|
||||
const item: GroupModel = { name: 'group-1' };
|
||||
|
||||
widget.onItemClick(item, null);
|
||||
expect(widget.field.value).toBe(item);
|
||||
@@ -96,8 +96,8 @@ describe('FunctionalGroupWidgetComponent', () => {
|
||||
|
||||
it('should flush selected value', () => {
|
||||
const groups: GroupModel[] = [
|
||||
new GroupModel({ id: '1', name: 'group 1' }),
|
||||
new GroupModel({ id: '2', name: 'group 2' })
|
||||
{ id: '1', name: 'group 1' },
|
||||
{ id: '2', name: 'group 2' }
|
||||
];
|
||||
|
||||
widget.groups = groups;
|
||||
@@ -110,8 +110,8 @@ describe('FunctionalGroupWidgetComponent', () => {
|
||||
|
||||
it('should be case insensitive when flushing value', () => {
|
||||
const groups: GroupModel[] = [
|
||||
new GroupModel({ id: '1', name: 'group 1' }),
|
||||
new GroupModel({ id: '2', name: 'gRoUp 2' })
|
||||
{ id: '1', name: 'group 1' },
|
||||
{ id: '2', name: 'gRoUp 2' }
|
||||
];
|
||||
|
||||
widget.groups = groups;
|
||||
@@ -123,10 +123,7 @@ describe('FunctionalGroupWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should fetch groups and show popup on key up', () => {
|
||||
const groups: GroupModel[] = [
|
||||
new GroupModel(),
|
||||
new GroupModel()
|
||||
];
|
||||
const groups: GroupModel[] = [{}, {}];
|
||||
spyOn(formService, 'getWorkflowGroups').and.returnValue(
|
||||
new Observable((observer) => {
|
||||
observer.next(groups);
|
||||
@@ -143,10 +140,7 @@ describe('FunctionalGroupWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should fetch groups with a group filter', () => {
|
||||
const groups: GroupModel[] = [
|
||||
new GroupModel(),
|
||||
new GroupModel()
|
||||
];
|
||||
const groups: GroupModel[] = [{}, {}];
|
||||
spyOn(formService, 'getWorkflowGroups').and.returnValue(
|
||||
new Observable((observer) => {
|
||||
observer.next(groups);
|
||||
|
@@ -60,7 +60,7 @@ export class FunctionalGroupWidgetComponent extends WidgetComponent implements O
|
||||
if (this.value) {
|
||||
this.formService
|
||||
.getWorkflowGroups(this.value, this.groupId)
|
||||
.subscribe((groupModel: GroupModel[]) => this.groups = groupModel || []);
|
||||
.subscribe(groups => this.groups = groups || []);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,10 +69,9 @@ export class FunctionalGroupWidgetComponent extends WidgetComponent implements O
|
||||
if (this.value && this.value.length >= this.minTermLength && this.oldValue !== this.value) {
|
||||
if (event.keyCode !== ESCAPE && event.keyCode !== ENTER) {
|
||||
this.oldValue = this.value;
|
||||
this.formService.getWorkflowGroups(this.value, this.groupId)
|
||||
.subscribe((group: GroupModel[]) => {
|
||||
this.groups = group || [];
|
||||
});
|
||||
this.formService
|
||||
.getWorkflowGroups(this.value, this.groupId)
|
||||
.subscribe(groups => this.groups = groups || []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
export * from './components/form-base.component';
|
||||
export * from './components/form-base.model';
|
||||
export * from './components/form-list.component';
|
||||
export * from './components/widgets/content/content.widget';
|
||||
export * from './components/form-renderer.component';
|
||||
|
Reference in New Issue
Block a user