diff --git a/docs/process-services-cloud/components/form-cloud.component.md b/docs/process-services-cloud/components/form-cloud.component.md index da267752ac..a1ed2c1c8c 100644 --- a/docs/process-services-cloud/components/form-cloud.component.md +++ b/docs/process-services-cloud/components/form-cloud.component.md @@ -252,13 +252,29 @@ There are two other functions that can be very useful when you need to control f ### Field Validators -You can supply a set of validator objects to the form using the `fieldValidators` -property. Each validator implements a check for a particular type of data (eg, a -date validator might check that the date in the field falls between 1980 and 2017). +You can supply a set of validator objects to the form using the `fieldValidators` property. To do this you must use Token `FORM_CLOUD_FIELD_VALIDATORS_TOKEN`. This is a A DI token that allows to inject additional form field validators. Each validator implements a check for a particular type of data (eg, a date validator might check that the date in the field falls between 1980 and 2017). ADF supplies a standard set of validators that handle most common cases but you can also implement your own custom validators to replace or extend the set. See the [Form Field Validator](../../core/interfaces/form-field-validator.interface.md) interface for full details and examples. +```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 {} + ``` + ### Common scenarios #### Rendering a form using form definition JSON diff --git a/docs/process-services-cloud/components/user-task-cloud.component.md b/docs/process-services-cloud/components/user-task-cloud.component.md index a3d5e89f5a..7c1c8c79da 100644 --- a/docs/process-services-cloud/components/user-task-cloud.component.md +++ b/docs/process-services-cloud/components/user-task-cloud.component.md @@ -15,7 +15,6 @@ Based on property taskDetails: TaskDetailsCloudModel shows a form or a screen. { expect(validator.validate(field)).toBe(false); }); + + it('should succeed for file viewer', () => { + const field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.ALFRESCO_FILE_VIEWER, + value: [{ sys_id: '123', sys_name: 'screenshot_123' }], + required: true + }); + + expect(validator.validate(field)).toBe(true); + }); + + it('should fail if file viewer has no value', () => { + const field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.ALFRESCO_FILE_VIEWER, + value: null, + required: true + }); + + expect(validator.validate(field)).toBe(false); + }); + + it('should succeed for properties viewer', () => { + const field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.PROPERTIES_VIEWER, + value: [{ sys_id: '123', sys_name: 'screenshot_123' }], + required: true + }); + + expect(validator.validate(field)).toBe(true); + }); + + it('should fail for properties viewer with no value', () => { + const field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.PROPERTIES_VIEWER, + value: null, + required: true + }); + + expect(validator.validate(field)).toBe(false); + }); }); describe('NumberFieldValidator', () => { diff --git a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts index 4945b5788a..0993927ba8 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts @@ -41,7 +41,9 @@ export class RequiredFieldValidator implements FormFieldValidator { FormFieldTypes.DYNAMIC_TABLE, FormFieldTypes.ATTACH_FOLDER, FormFieldTypes.DECIMAL, - FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY + FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY, + FormFieldTypes.ALFRESCO_FILE_VIEWER, + FormFieldTypes.PROPERTIES_VIEWER ]; isSupported(field: FormFieldModel): boolean { diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts index d718293738..02e09d0b1d 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts @@ -59,13 +59,14 @@ import { import { FormCloudRepresentation } from '../models/form-cloud-representation.model'; import { FormCloudService } from '../services/form-cloud.service'; import { DisplayModeService } from '../services/display-mode.service'; -import { FormCloudComponent } from './form-cloud.component'; +import { FORM_CLOUD_FIELD_VALIDATORS_TOKEN, FormCloudComponent } from './form-cloud.component'; import { MatButtonHarness } from '@angular/material/button/testing'; import { FormCloudDisplayMode } from '../../services/form-fields.interfaces'; import { CloudFormRenderingService } from './cloud-form-rendering.service'; import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module'; import { TaskVariableCloud } from '../models/task-variable-cloud.model'; import { ProcessServicesCloudModule } from '../../process-services-cloud.module'; +import { FormFieldValidator } from '../../../../../core/src/public-api'; const mockOauth2Auth: any = { oauth2Auth: { @@ -75,6 +76,12 @@ const mockOauth2Auth: any = { reply: jasmine.createSpy('reply') }; +const fakeValidator = { + supportedTypes: ['test'], + isSupported: () => true, + validate: () => true +} as FormFieldValidator; + describe('FormCloudComponent', () => { let formCloudService: FormCloudService; let fixture: ComponentFixture; @@ -113,7 +120,8 @@ describe('FormCloudComponent', () => { provide: VersionCompatibilityService, useValue: {} }, - { provide: FormRenderingService, useClass: CloudFormRenderingService } + { provide: FormRenderingService, useClass: CloudFormRenderingService }, + { provide: FORM_CLOUD_FIELD_VALIDATORS_TOKEN, useValue: [fakeValidator] } ] }); const apiService = TestBed.inject(AlfrescoApiService); @@ -1176,6 +1184,13 @@ describe('FormCloudComponent', () => { expect(formComponent.disableSaveButton).toBeTrue(); }); + it('should set field validators with injected validators', () => { + formComponent.formCloudRepresentationJSON = new FormCloudRepresentation(JSON.parse(JSON.stringify(cloudFormMock))); + const form = formComponent.parseForm(formComponent.formCloudRepresentationJSON); + expect(formComponent.fieldValidators.length).toBe(1); + expect(form.fieldValidators.length).toBe(10); + }); + describe('form validations', () => { it('should be able to set visibility conditions for Attach File widget', async () => { spyOn(formCloudService, 'getForm').and.returnValue(of(conditionalUploadWidgetsMock)); diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts index 6c8b830bfb..6fca94124e 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts @@ -21,10 +21,13 @@ import { DestroyRef, EventEmitter, HostListener, + Inject, inject, + InjectionToken, Input, OnChanges, OnInit, + Optional, Output, SimpleChanges } from '@angular/core'; @@ -33,7 +36,6 @@ import { filter, map, switchMap } from 'rxjs/operators'; import { ConfirmDialogComponent, ContentLinkModel, - FORM_FIELD_VALIDATORS, FormatSpacePipe, FormBaseComponent, FormEvent, @@ -66,6 +68,8 @@ import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { A11yModule } from '@angular/cdk/a11y'; +export const FORM_CLOUD_FIELD_VALIDATORS_TOKEN = new InjectionToken('FORM_CLOUD_FIELD_VALIDATORS_TOKEN'); + @Component({ selector: 'adf-cloud-form', standalone: true, @@ -110,10 +114,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, @Input() data: TaskVariableCloud[]; - /** FormFieldValidator allow to override the form field validators provided. */ - @Input() - fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS]; - /** * The available display configurations for the form */ @@ -151,6 +151,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, protected subscriptions: Subscription[] = []; nodeId: string; formCloudRepresentationJSON: any; + fieldValidators: FormFieldValidator[] = []; readonly id: string; displayMode: string; @@ -167,9 +168,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, private readonly destroyRef = inject(DestroyRef); - constructor() { + constructor(@Optional() @Inject(FORM_CLOUD_FIELD_VALIDATORS_TOKEN) injectedFieldValidators?: FormFieldValidator[]) { super(); - + this.loadInjectedFieldValidators(injectedFieldValidators); this.spinnerService.initSpinnerHandling(this.destroyRef); this.id = uuidGeneration(); @@ -410,13 +411,10 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, formValues[variable.name] = variable.value; }); - const form = new FormModel(formCloudRepresentationJSON, formValues, this.readOnly, this.formService); + const form = new FormModel(formCloudRepresentationJSON, formValues, this.readOnly, this.formService, undefined, this.fieldValidators); if (!form) { form.outcomes = this.getFormDefinitionOutcomes(form); } - if (this.fieldValidators && this.fieldValidators.length > 0) { - form.fieldValidators = this.fieldValidators; - } return form; } @@ -506,4 +504,10 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, findDisplayConfiguration(displayMode?: string): FormCloudDisplayModeConfiguration { return this.displayModeService.findConfiguration(FormCloudDisplayMode[displayMode], this.displayModeConfigurations); } + + loadInjectedFieldValidators(injectedFieldValidators: FormFieldValidator[]): void { + if (injectedFieldValidators && injectedFieldValidators?.length) { + this.fieldValidators = [...this.fieldValidators, ...injectedFieldValidators]; + } + } } diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html index e173bcdec3..5491ffbb63 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html @@ -78,7 +78,6 @@ [data]="resolvedValues" [formId]="processDefinitionCurrent.formKey" [displayModeConfigurations]="displayModeConfigurations" - [fieldValidators]="fieldValidators" [showSaveButton]="false" [showCompleteButton]="false" [showRefreshButton]="false" diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts index 33f677a027..fd5ab50ecb 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts @@ -17,7 +17,7 @@ import { SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { FORM_FIELD_VALIDATORS, FormModel } from '@alfresco/adf-core'; +import { FormModel } from '@alfresco/adf-core'; import { of, throwError } from 'rxjs'; import { StartProcessCloudService } from '../services/start-process-cloud.service'; import { FormCloudService } from '../../../form/services/form-cloud.service'; @@ -31,8 +31,7 @@ import { fakeNoNameProcessDefinitions, fakeSingleProcessDefinition, fakeSingleProcessDefinitionWithoutForm, - fakeFormModelJson, - MockFormFieldValidator + fakeFormModelJson } from '../mock/start-process.component.mock'; import { By } from '@angular/platform-browser'; import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; @@ -691,22 +690,6 @@ describe('StartProcessCloudComponent', () => { const processDefinitionInput = fixture.nativeElement.querySelector('#processDefinitionName'); expect(processDefinitionInput.textContent).toEqual(''); }); - - it('should append additional field validators to the default ones when provided', () => { - const mockFirstCustomFieldValidator = new MockFormFieldValidator(); - const mockSecondCustomFieldValidator = new MockFormFieldValidator(); - - component.fieldValidators = [mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]; - fixture.detectChanges(); - - expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS, mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]); - }); - - it('should use default field validators when no additional validators are provided', () => { - fixture.detectChanges(); - - expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS]); - }); }); describe('start process', () => { diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts index 9b20f4c6cc..21f6ca6ebe 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts @@ -29,15 +29,7 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; -import { - ContentLinkModel, - FORM_FIELD_VALIDATORS, - FormFieldValidator, - FormModel, - InplaceFormInputComponent, - LocalizedDatePipe, - TranslationService -} from '@alfresco/adf-core'; +import { ContentLinkModel, FormModel, InplaceFormInputComponent, LocalizedDatePipe, TranslationService } from '@alfresco/adf-core'; import { AbstractControl, FormControl, FormGroup, ReactiveFormsModule, ValidatorFn, Validators } from '@angular/forms'; import { MatAutocompleteModule, MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { catchError, debounceTime } from 'rxjs/operators'; @@ -118,10 +110,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { @Input() values: TaskVariableCloud[]; - /** FormFieldValidator allow to provide additional validators to the form field. */ - @Input() - fieldValidators: FormFieldValidator[]; - /** Show/hide the process dropdown list. */ @Input() showSelectProcessDropdown: boolean = true; @@ -235,8 +223,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { } ngOnInit() { - this.initFieldValidators(); - this.processDefinition.setValue(this.processDefinitionName); this.processDefinition.valueChanges .pipe(debounceTime(PROCESS_DEFINITION_DEBOUNCE)) @@ -270,10 +256,6 @@ export class StartProcessCloudComponent implements OnChanges, OnInit { this.formCloud = form; } - private initFieldValidators(): void { - this.fieldValidators = this.fieldValidators ? [...FORM_FIELD_VALIDATORS, ...this.fieldValidators] : [...FORM_FIELD_VALIDATORS]; - } - private getMaxNameLength(): number { return this.maxNameLength > MAX_NAME_LENGTH ? MAX_NAME_LENGTH : this.maxNameLength; } diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html index 233ffabda5..8aa9e99bf5 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html @@ -12,7 +12,6 @@ [showCompleteButton]="canCompleteTask()" [showSaveButton]="canCompleteTask()" [displayModeConfigurations]="displayModeConfigurations" - [fieldValidators]="fieldValidators" (formSaved)="onFormSaved($event)" (formCompleted)="onFormCompleted($event)" (formError)="onError($event)" diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts index f6e798151f..02bc2e78d2 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FORM_FIELD_VALIDATORS, FormFieldModel, FormFieldValidator, FormModel, FormOutcomeEvent, FormOutcomeModel } from '@alfresco/adf-core'; +import { FormModel, FormOutcomeEvent, FormOutcomeModel } from '@alfresco/adf-core'; import { FormCustomOutcomesComponent } from '@alfresco/adf-process-services-cloud'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { of } from 'rxjs'; @@ -51,16 +51,6 @@ const taskDetails: TaskDetailsCloudModel = { permissions: [TASK_VIEW_PERMISSION] }; -class MockFormFieldValidator implements FormFieldValidator { - isSupported(_field: FormFieldModel): boolean { - return true; - } - - validate(_field: FormFieldModel): boolean { - return true; - } -} - describe('TaskFormCloudComponent', () => { let taskCloudService: TaskCloudService; let identityUserService: IdentityUserService; @@ -201,21 +191,6 @@ describe('TaskFormCloudComponent', () => { const canUnclaimTask = component.canUnclaimTask(); expect(canUnclaimTask).toBe(false); }); - - it('should append additional field validators to the default ones when provided', () => { - const mockFirstCustomFieldValidator = new MockFormFieldValidator(); - const mockSecondCustomFieldValidator = new MockFormFieldValidator(); - fixture.componentRef.setInput('fieldValidators', [mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]); - fixture.detectChanges(); - - expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS, mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]); - }); - - it('should use default field validators when no additional validators are provided', () => { - fixture.detectChanges(); - - expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS]); - }); }); describe('Events', () => { diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts index 8cc0e6b74a..913286f21e 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { ContentLinkModel, FORM_FIELD_VALIDATORS, FormFieldValidator, FormModel, FormOutcomeEvent, FormRenderingService } from '@alfresco/adf-core'; -import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core'; +import { ContentLinkModel, FormModel, FormOutcomeEvent, FormRenderingService } from '@alfresco/adf-core'; +import { Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation } from '@angular/core'; import { FormCloudComponent } from '../../../../form/components/form-cloud.component'; import { AttachFileCloudWidgetComponent } from '../../../../form/components/widgets/attach-file/attach-file-cloud-widget.component'; import { DateCloudWidgetComponent } from '../../../../form/components/widgets/date/date-cloud.widget'; @@ -36,7 +36,7 @@ import { FormCustomOutcomesComponent } from '../../../../form/components/form-cl styleUrls: ['./task-form-cloud.component.scss'], encapsulation: ViewEncapsulation.None }) -export class TaskFormCloudComponent implements OnInit { +export class TaskFormCloudComponent { /** App id to fetch corresponding form and values. */ @Input() appName: string = ''; @@ -83,10 +83,6 @@ export class TaskFormCloudComponent implements OnInit { @Input() displayModeConfigurations: FormCloudDisplayModeConfiguration[]; - /** FormFieldValidator allow to provide additional validators to the form field. */ - @Input() - fieldValidators: FormFieldValidator[]; - /** Task details. */ @Input() taskDetails: TaskDetailsCloudModel; @@ -149,14 +145,6 @@ export class TaskFormCloudComponent implements OnInit { this.formRenderingService.setComponentTypeResolver('date', () => DateCloudWidgetComponent, true); } - ngOnInit() { - this.initFieldValidators(); - } - - private initFieldValidators() { - this.fieldValidators = this.fieldValidators ? [...FORM_FIELD_VALIDATORS, ...this.fieldValidators] : [...FORM_FIELD_VALIDATORS]; - } - hasForm(): boolean { return this.taskDetails && !!this.taskDetails.formKey; } diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html index 1424e8f21c..a76982f871 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html @@ -8,7 +8,6 @@ [candidateUsers]="candidateUsers" [candidateGroups]="candidateGroups" [displayModeConfigurations]="displayModeConfigurations" - [fieldValidators]="fieldValidators" [showValidationIcon]="showValidationIcon" [showTitle]="showTitle" [taskId]="taskId" diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts index 7893f61eaf..f7aa887117 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { ContentLinkModel, EmptyContentComponent, FormFieldValidator, FormModel, FormOutcomeEvent } from '@alfresco/adf-core'; +import { ContentLinkModel, EmptyContentComponent, FormModel, FormOutcomeEvent } from '@alfresco/adf-core'; import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormCloudDisplayModeConfiguration } from '../../../../services/form-fields.interfaces'; @@ -72,10 +72,6 @@ export class UserTaskCloudComponent implements OnInit, OnChanges { @Input() displayModeConfigurations: FormCloudDisplayModeConfiguration[]; - /** FormFieldValidator allow to provide additional validators to the form field. */ - @Input() - fieldValidators: FormFieldValidator[]; - /** Toggle readonly state of the task. */ @Input() readOnly = false;