[AAE-30863] updated validators to set proper button state (#10667)

This commit is contained in:
tomasz hanaj 2025-02-25 09:30:14 +01:00 committed by GitHub
parent 1e83be9194
commit 0c725a1c90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 102 additions and 106 deletions

View File

@ -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

View File

@ -15,7 +15,6 @@ Based on property taskDetails: TaskDetailsCloudModel shows a form or a screen.
<adf-cloud-user-task
[appName]="appName"
[displayModeConfigurations]="displayConfigurations"
[fieldValidators]="formFieldValidators"
[showTitle]="false"
[showValidationIcon]="false"
[taskId]="taskId"
@ -38,7 +37,6 @@ Based on property taskDetails: TaskDetailsCloudModel shows a form or a screen.
|---------------------------|---------------------------------------|---------------|---------------------------------------------------|
| appName | `string` | "" | App id to fetch corresponding form and values. |
| readOnly | `boolean` | false | Toggle readonly state of the task. |
| fieldValidators | `FormFieldValidator[]` | | Allows to provide additional validators to the form field. |
| showCancelButton | `boolean` | true | Toggle rendering of the `Cancel` button. |
| showCompleteButton | `boolean` | true | Toggle rendering of the `Complete` button. |
| showTitle | `boolean` | true | Toggle rendering of the form title. |

View File

@ -161,6 +161,46 @@ describe('FormFieldValidator', () => {
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', () => {

View File

@ -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 {

View File

@ -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<FormCloudComponent>;
@ -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));

View File

@ -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<FormFieldValidator[]>('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];
}
}
}

View File

@ -78,7 +78,6 @@
[data]="resolvedValues"
[formId]="processDefinitionCurrent.formKey"
[displayModeConfigurations]="displayModeConfigurations"
[fieldValidators]="fieldValidators"
[showSaveButton]="false"
[showCompleteButton]="false"
[showRefreshButton]="false"

View File

@ -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', () => {

View File

@ -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;
}

View File

@ -12,7 +12,6 @@
[showCompleteButton]="canCompleteTask()"
[showSaveButton]="canCompleteTask()"
[displayModeConfigurations]="displayModeConfigurations"
[fieldValidators]="fieldValidators"
(formSaved)="onFormSaved($event)"
(formCompleted)="onFormCompleted($event)"
(formError)="onError($event)"

View File

@ -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', () => {

View File

@ -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;
}

View File

@ -8,7 +8,6 @@
[candidateUsers]="candidateUsers"
[candidateGroups]="candidateGroups"
[displayModeConfigurations]="displayModeConfigurations"
[fieldValidators]="fieldValidators"
[showValidationIcon]="showValidationIcon"
[showTitle]="showTitle"
[taskId]="taskId"

View File

@ -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;