AAE-30864 Refactored services to accept injected validators (#10660)

* [AAE-30864] refactored services to accept injected validators

* [AAE-30864] updated documentation, applied pr comments
This commit is contained in:
tomasz hanaj 2025-02-20 09:22:17 +01:00 committed by GitHub
parent 70d899f5ba
commit f39f104d45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 249 additions and 15 deletions

View File

@ -0,0 +1,114 @@
---
Title: Form model
Added: 2025-02-19
Status: Active
Last reviewed: 2025-02-19
---
# [Form model](../../../lib/core/src/lib/form/components/widgets/core/form.model.ts "Defined in form.model.ts")
Contains the value and metadata for a form.
## Properties
| Name | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
|UNSET_TASK_NAME| string | 'Nameless task'|static property|
|SAVE_OUTCOME| string | '$save'|static property|
|COMPLETE_OUTCOME| string | '$complete'|static property|
|START_PROCESS_OUTCOME| string | '$startProcess'|static property|
|id| string | number||id of form|
|name| string||form name|
|taskId| string||task id|
|confirmMessage| ConfirmMessage||confirmation message|
|taskName |string| FormModel.UNSET_TASK_NAME|task name|
|processDefinitionId| string||Process definition id |
|selectedOutcome| string||selected outcome|
|enableFixedSpace| boolean||should fixed space be enabled|
|displayMode| any||which mode should be displayed|
|fieldsCache| FormFieldModel[] | []|cache for fields|
|json| any||json with form configuration|
|nodeId| string||id of node|
|values| FormValues | {}|form values|
|tabs| TabModel[] | []|tabs|
|fields| (ContainerModel | FormFieldModel)[] | []|form fields|
|outcomes| FormOutcomeModel[] | []|set of outcomes|
|fieldValidators| FormFieldValidator[] | []|validators for fields|
|customFieldTemplates| FormFieldTemplates | {}|custom templates|
|theme?| ThemeModel||theme|
|className| string||class name|
|readOnly | false||is form read only|
|isValid | true||is form valid|
|processVariables| ProcessVariableModel[] | []|process variables|
|variables| FormVariableModel[] | []|variables|
## Methods
- `onFormFieldChanged(field: FormFieldModel)`
Triggered when field is changed. Validates field and calls FormService
- `validateForm(): void`
Validates entire form and all form fields.
- `validateField(field: FormFieldModel): void`
Validates a specific form field, triggers form validation.
- `parseRootFields(json: any): (ContainerModel | FormFieldModel)[]`
Activiti supports 3 types of root fields: container|group|dynamic-table
- `loadData(formValues: FormValues)`
Loads external data and overrides field values. Typically used when form definition and form data coming from different sources
- `canOverrideFieldValueWithProcessValue(field: FormFieldModel, variableId: string, formValues: FormValues): boolean`
Checks if field value can be overriden with process value
- `isDefined(value: string): boolean`
Check if variable is defined
- `getFormVariable(identifier: string): FormVariableModel`
Returns a form variable that matches the identifier.
- `getDefaultFormVariableValue(identifier: string): any`
Returns a value of the form variable that matches the identifier. Provides additional conversion of types (date, boolean).
- `getProcessVariableValue(name: string): any`
Returns a process variable value. When mapping a process variable with a form variable the mapping is already resolved by the rest API with the name of variables.formVariableName.
- `parseValue(type: string, value: any): any`
Parse value data and boolean
- `hasTabs(): boolean`
Check if form has tabs
- `hasFields(): boolean`
Check if there are any fields
- `hasOutcomes(): boolean`
Check if form has outcomes
- `getFieldById(fieldId: string): FormFieldModel`
Find field by id
- `getFormFields(filterTypes?: string[]): FormFieldModel[]`
Get form fields
- `processFields(fields: (ContainerModel | FormFieldModel)[], formFieldModel: FormFieldModel[]): void`
Process fields
- `isContainerField(field: ContainerModel | FormFieldModel): field is ContainerModel`
Check if it is container
- `isSectionField(field: ContainerModel | FormFieldModel): field is FormFieldModel`
Check if it is section
- `handleSectionField(section: FormFieldModel, formFieldModel: FormFieldModel[]): void`
Handle section
- `handleContainerField(container: ContainerModel, formFieldModel: FormFieldModel[]): void`
Handle container
- `handleSingleField(field: FormFieldModel, formFieldModel: FormFieldModel[]): void`
Handle single field
- `filterFieldsByType(fields: FormFieldModel[], types?: string[]): FormFieldModel[]`
Filter fields based on type
- `markAsInvalid(): void`
Set form as invalid
- `parseOutcomes()`
Parse outcomes from json
- `addValuesNotPresent(valuesToSetIfNotPresent: FormValues)`
Set values if they are not present
- `isValidDropDown(key: string): boolean`
Validates dropdown
- `setNodeIdValueForViewersLinkedToUploadWidget(linkedUploadWidgetContentSelected: UploadWidgetContentLinkModel)`
Set node id
- `changeFieldVisibility(fieldId: string, visibility: boolean): void`
Changes field visibility
- `changeFieldDisabled(fieldId: string, disabled: boolean): void`
Changes disabled status of field
- `changeFieldRequired(fieldId: string, required: boolean): void`
Changes required status of field
- `changeFieldValue(fieldId: string, value: any): void`
Changes field value
- `changeVariableValue(variableId: string, value: any): void`
Changes variable value
- `loadInjectedFieldValidators(injectedFieldValidators: FormFieldValidator[]): void`
Checks it there are any injectedValidators and adds them to the array of field validators.

View File

@ -191,3 +191,32 @@ class MyComponent {
- `handleError(error: any):`[`Observable`](http://reactivex.io/documentation/observable.html)`<any>`
Reports an error message.
- `error` - Data object with optional \`message\` and \`status\` fields for the error
### Properties
| Name | Type | Description |
| ---- | --------- | ----------- |
| fieldValidators | FormFieldValidator[] | Array of Field Validators injected with token and then passed to FormModel |
### Inject Preference service
Token: [`FORM_SERVICE_FIELD_VALIDATORS_TOKEN`]
A DI token that allows to inject additional form field validators.
```ts
import { NgModule } from '@angular/core';
import { FORM_SERVICE_FIELD_VALIDATORS_TOKEN } from '@alfresco/adf-core';
@NgModule({
imports: [
...Import Required Modules
],
providers: [
{
provide: FORM_SERVICE_FIELD_VALIDATORS_TOKEN,
useValue: [new AdditionalFormFieldValidator()]
}
]
})
export class ExampleModule {}
```

View File

@ -92,6 +92,36 @@ class MyComponent {
- _values:_ [`FormValues`](../../../lib/core/src/lib/form/components/widgets/core/form-values.ts) - [Form](../../../lib/process-services/src/lib/task-list/models/form.model.ts) values object
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`TaskDetailsCloudModel`](../../../lib/process-services-cloud/src/lib/task/models/task-details-cloud.model.ts)`>` - Updated task details
### Properties
| Name | Type | Description |
| ---- | --------- | ----------- |
| fieldValidators | FormFieldValidator[] | Array of Field Validators injected with token and then passed to FormModel |
### Inject Preference service
Token: [`FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN`]
A DI token that allows to inject additional form field validators.
```ts
import { NgModule } from '@angular/core';
import { FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN } from '@alfresco/adf-process-services-cloud';
@NgModule({
imports: [
...Import Required Modules
],
providers: [
{
provide: FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN,
useValue: [new AdditionalFormFieldValidator()]
}
]
})
export class ExampleModule {}
```
## See also
- [Form cloud component](../components/form-cloud.component.md)

View File

@ -1686,3 +1686,9 @@ export const mockFormWithSections = {
}
}
};
export const fakeValidatorMock = {
supportedTypes: ['test'],
isSupported: () => true,
validate: () => true
};

View File

@ -24,7 +24,7 @@ import { FormFieldModel } from './form-field.model';
import { FormOutcomeModel } from './form-outcome.model';
import { FormModel } from './form.model';
import { TabModel } from './tab.model';
import { fakeMetadataForm, mockDisplayExternalPropertyForm, mockFormWithSections } from '../../mock/form.mock';
import { fakeMetadataForm, mockDisplayExternalPropertyForm, mockFormWithSections, fakeValidatorMock } from '../../mock/form.mock';
import { CoreTestingModule } from '../../../../testing';
import { TestBed } from '@angular/core/testing';
@ -433,6 +433,13 @@ describe('FormModel', () => {
expect(FORM_FIELD_VALIDATORS.length).toBe(defaultLength);
});
it('should include injected field validators', () => {
const form = new FormModel({}, null, false, formService, undefined, [fakeValidatorMock]);
const defaultLength = FORM_FIELD_VALIDATORS.length;
expect(form.fieldValidators.length).toBe(defaultLength + 1);
});
describe('variables', () => {
let form: FormModel;

View File

@ -85,7 +85,7 @@ export class FormModel implements ProcessFormModel {
tabs: TabModel[] = [];
fields: (ContainerModel | FormFieldModel)[] = [];
outcomes: FormOutcomeModel[] = [];
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
fieldValidators: FormFieldValidator[] = [];
customFieldTemplates: FormFieldTemplates = {};
theme?: ThemeModel;
@ -100,7 +100,8 @@ export class FormModel implements ProcessFormModel {
formValues?: FormValues,
readOnly: boolean = false,
protected formService?: FormValidationService,
enableFixedSpace?: boolean
enableFixedSpace?: boolean,
injectedFieldValidators?: FormFieldValidator[]
) {
this.readOnly = readOnly;
this.json = json;
@ -133,6 +134,7 @@ export class FormModel implements ProcessFormModel {
this.parseOutcomes();
}
this.loadInjectedFieldValidators(injectedFieldValidators);
this.validateForm();
}
@ -501,4 +503,8 @@ export class FormModel implements ProcessFormModel {
variable.value = value;
}
}
private loadInjectedFieldValidators(injectedFieldValidators: FormFieldValidator[]): void {
this.fieldValidators = injectedFieldValidators ? [...FORM_FIELD_VALIDATORS, ...injectedFieldValidators] : [...FORM_FIELD_VALIDATORS];
}
}

View File

@ -17,15 +17,23 @@
import { TestBed } from '@angular/core/testing';
import { formModelTabs } from '../../mock';
import { FormService } from './form.service';
import { FORM_SERVICE_FIELD_VALIDATORS_TOKEN, FormService } from './form.service';
import { CoreTestingModule } from '../../testing';
import { FORM_FIELD_VALIDATORS, FormFieldValidator } from '../public-api';
const fakeValidator = {
supportedTypes: ['test'],
isSupported: () => true,
validate: () => true
} as FormFieldValidator;
describe('Form service', () => {
let service: FormService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule]
imports: [CoreTestingModule],
providers: [{ provide: FORM_SERVICE_FIELD_VALIDATORS_TOKEN, useValue: [fakeValidator] }]
});
service = TestBed.inject(FormService);
});
@ -36,5 +44,11 @@ describe('Form service', () => {
const formParsed = service.parseForm(formModelTabs);
expect(formParsed).toBeDefined();
});
it('should return form with injected field validators', () => {
expect(formModelTabs.formRepresentation.formDefinition).toBeDefined();
const formParsed = service.parseForm(formModelTabs);
expect(formParsed.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS, fakeValidator]);
});
});
});

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Inject, Injectable, InjectionToken, Optional } from '@angular/core';
import { Subject } from 'rxjs';
import { ContentLinkModel } from '../components/widgets/core/content-link.model';
import { FormOutcomeEvent } from '../components/widgets/core/form-outcome-event.model';
@ -30,12 +30,15 @@ import { ValidateFormFieldEvent } from '../events/validate-form-field.event';
import { FormValidationService } from './form-validation-service.interface';
import { FormRulesEvent } from '../events/form-rules.event';
import { FormSpinnerEvent } from '../events';
import { FormFieldModel } from '../components/widgets';
import { FormFieldModel, FormFieldValidator } from '../components/widgets';
export const FORM_SERVICE_FIELD_VALIDATORS_TOKEN = new InjectionToken<FormFieldValidator[]>('FORM_SERVICE_FIELD_VALIDATORS_TOKEN');
@Injectable({
providedIn: 'root'
})
export class FormService implements FormValidationService {
private fieldValidators: FormFieldValidator[];
formLoaded = new Subject<FormEvent>();
formDataRefreshed = new Subject<FormEvent>();
formFieldValueChanged = new Subject<FormFieldEvent>();
@ -59,7 +62,9 @@ export class FormService implements FormValidationService {
formRulesEvent = new Subject<FormRulesEvent>();
constructor() {}
constructor(@Optional() @Inject(FORM_SERVICE_FIELD_VALIDATORS_TOKEN) injectedFieldValidators?: FormFieldValidator[]) {
this.fieldValidators = injectedFieldValidators || [];
}
/**
* Parses JSON data to create a corresponding Form model.
@ -72,7 +77,7 @@ export class FormService implements FormValidationService {
*/
parseForm(json: any, data?: FormValues, readOnly: boolean = false, fixedSpace?: boolean): FormModel {
if (json) {
const form = new FormModel(json, data, readOnly, this, fixedSpace);
const form = new FormModel(json, data, readOnly, this, fixedSpace, this.fieldValidators);
if (!json.fields) {
form.outcomes = [
new FormOutcomeModel(form, {

View File

@ -16,10 +16,11 @@
*/
import { TestBed } from '@angular/core/testing';
import { FormCloudService } from './form-cloud.service';
import { FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN, FormCloudService } from './form-cloud.service';
import { of } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { FORM_FIELD_VALIDATORS, FormFieldValidator } from '@alfresco/adf-core';
const mockTaskResponseBody = {
entry: { id: 'id', name: 'name', formKey: 'form-key' }
@ -27,6 +28,12 @@ const mockTaskResponseBody = {
const mockFormResponseBody = { formRepresentation: { id: 'form-id', name: 'task-form', taskId: 'task-id' } };
const fakeValidator = {
supportedTypes: ['test'],
isSupported: () => true,
validate: () => true
} as FormFieldValidator;
describe('Form Cloud service', () => {
let service: FormCloudService;
let adfHttpClient: AdfHttpClient;
@ -37,7 +44,8 @@ describe('Form Cloud service', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ProcessServiceCloudTestingModule]
imports: [ProcessServiceCloudTestingModule],
providers: [{ provide: FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN, useValue: [fakeValidator] }]
});
service = TestBed.inject(FormCloudService);
adfHttpClient = TestBed.inject(AdfHttpClient);
@ -68,6 +76,14 @@ describe('Form Cloud service', () => {
expect(result.id).toBe(formId);
expect(result.name).toBe('task-form');
});
it('should create form with injected validators', () => {
const formId = 'form-id';
const json = { formRepresentation: { id: formId, name: 'task-form', taskId: 'task-id', formDefinition: {} } };
const result = service.parseForm(json, undefined, undefined);
expect(result).toBeDefined();
expect(result.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS, fakeValidator]);
});
});
describe('Task tests', () => {

View File

@ -15,8 +15,8 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { FormValues, FormModel, FormFieldOption } from '@alfresco/adf-core';
import { Inject, Injectable, InjectionToken, Optional } from '@angular/core';
import { FormValues, FormModel, FormFieldOption, FormFieldValidator } from '@alfresco/adf-core';
import { Observable, from, EMPTY } from 'rxjs';
import { expand, map, reduce, switchMap } from 'rxjs/operators';
import { TaskDetailsCloudModel } from '../../task/models/task-details-cloud.model';
@ -27,18 +27,25 @@ import { FormContent } from '../../services/form-fields.interfaces';
import { FormCloudServiceInterface } from './form-cloud.service.interface';
import { AdfHttpClient } from '@alfresco/adf-core/api';
export const FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN = new InjectionToken<FormFieldValidator[]>('FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN');
@Injectable({
providedIn: 'root'
})
export class FormCloudService extends BaseCloudService implements FormCloudServiceInterface {
private _uploadApi: UploadApi;
private fieldValidators: FormFieldValidator[];
get uploadApi(): UploadApi {
this._uploadApi = this._uploadApi ?? new UploadApi(this.apiService.getInstance());
return this._uploadApi;
}
constructor(adfHttpClient: AdfHttpClient) {
constructor(
adfHttpClient: AdfHttpClient,
@Optional() @Inject(FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN) injectedFieldValidators?: FormFieldValidator[]
) {
super(adfHttpClient);
this.fieldValidators = injectedFieldValidators || [];
}
/**
@ -219,7 +226,7 @@ export class FormCloudService extends BaseCloudService implements FormCloudServi
formValues[variable.name] = variable.value;
});
return new FormModel(flattenForm, formValues, readOnly);
return new FormModel(flattenForm, formValues, readOnly, undefined, undefined, this.fieldValidators);
}
return null;
}