Revert "AAE-23521 Fix improve dropdown reactive form" (#10068)

* Revert "AAE-23521 Fix improve dropdown reactive form (#10040)"

This reverts commit f88ff10f30.

* Revert "AAE-24371 Fix invalid endpoint is called when process with form is st… (#10052)"

This reverts commit d38e782fe3.

* Revert "Revert "AAE-24371 Fix invalid endpoint is called when process with form is st… (#10052)""

This reverts commit 125ada76f61ab7a258503dbc51d09950073f9a95.
This commit is contained in:
Vito Albano
2024-08-09 15:01:44 +01:00
committed by GitHub
parent 7d741c2634
commit 554218d11e
13 changed files with 331 additions and 466 deletions

View File

@@ -15,19 +15,7 @@
* limitations under the License.
*/
import {
Component,
EventEmitter,
Input,
Output,
ViewEncapsulation,
SimpleChanges,
OnInit,
OnDestroy,
OnChanges,
inject,
ChangeDetectorRef
} from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewEncapsulation, SimpleChanges, OnInit, OnDestroy, OnChanges, inject } from '@angular/core';
import {
WidgetVisibilityService,
FormService,
@@ -76,7 +64,6 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
protected visibilityService = inject(WidgetVisibilityService);
protected ecmModelService = inject(EcmModelService);
protected nodeService = inject(NodesApiService);
private cdRef = inject(ChangeDetectorRef);
/** Underlying form model instance. */
@Input()
@@ -144,7 +131,6 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
this.formService.validateForm.pipe(takeUntil(this.onDestroy$)).subscribe((validateFormEvent) => {
if (validateFormEvent.errorsField.length > 0) {
this.formError.next(validateFormEvent.errorsField);
this.cdRef.detectChanges();
}
});
}

View File

@@ -1,16 +1,23 @@
<div
class="adf-dropdown-widget {{field.className}}"
[class.adf-invalid]="dropdownControl.invalid && dropdownControl.touched"
[class.adf-readonly]="field.readOnly"
>
<div class="adf-dropdown-widget {{field.className}}"
[class.adf-invalid]="!field.isValid && isTouched()" [class.adf-readonly]="field.readOnly">
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
<mat-form-field>
<mat-select class="adf-select" [id]="field.id" [formControl]="dropdownControl">
<mat-option *ngFor="let opt of field.options" [value]="opt" [id]="opt.id">{{opt.name}}</mat-option>
<mat-option id="readonlyOption" *ngIf="dropdownControl.disabled" [value]="field.value">{{field.value}}</mat-option>
<mat-select class="adf-select"
[id]="field.id"
[(ngModel)]="field.value"
[disabled]="field.readOnly"
(ngModelChange)="onFieldChanged(field)"
(blur)="markAsTouched()">
<mat-option *ngFor="let opt of field.options"
[value]="getOptionValue(opt, field.value)"
[id]="opt.id">{{opt.name}}
</mat-option>
<mat-option id="readonlyOption" *ngIf="isReadOnlyType()" [value]="field.value">{{field.value}}</mat-option>
</mat-select>
</mat-form-field>
<ng-container *ngIf="!isReadOnlyField && dropdownControl.touched">
<ng-container *ngIf="!isReadOnlyField">
<error-widget [error]="field.validationSummary"></error-widget>
<error-widget class="adf-dropdown-required-message" *ngIf="showRequiredMessage()"
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</ng-container>
</div>

View File

@@ -139,12 +139,9 @@ describe('DropdownWidgetComponent', () => {
describe('when is required', () => {
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
id: 'dropdown-id',
type: FormFieldTypes.DROPDOWN,
required: true
});
widget.ngOnInit();
});
it('should be able to display label with asterisk', async () => {
@@ -160,9 +157,8 @@ describe('DropdownWidgetComponent', () => {
it('should be invalid if no default option after interaction', async () => {
expect(element.querySelector('.adf-invalid')).toBeFalsy();
const dropdown = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown-id' }));
await dropdown.focus();
await dropdown.blur();
const dropdownSelect = element.querySelector('.adf-select');
dropdownSelect.dispatchEvent(new Event('blur'));
fixture.detectChanges();
await fixture.whenStable();
@@ -190,14 +186,12 @@ describe('DropdownWidgetComponent', () => {
id: 'dropdown-id',
name: 'date-name',
type: 'dropdown',
readOnly: false,
readOnly: 'false',
restUrl: 'fake-rest-url',
optionType: 'rest'
});
widget.field.emptyOption = { id: 'empty', name: 'Choose one...' };
widget.field.isVisible = true;
widget.ngOnInit();
fixture.detectChanges();
});
@@ -219,8 +213,9 @@ describe('DropdownWidgetComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
const dropdown = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown-id' }));
expect(await dropdown.getValueText()).toBe('option_2');
const dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('option_2');
expect(dropDownElement.attributes['ng-reflect-model'].textContent).toBe('option_2');
});
it('should select the empty value when no default is chosen', async () => {
@@ -229,9 +224,8 @@ describe('DropdownWidgetComponent', () => {
await (await loader.getHarness(MatSelectHarness)).open();
const dropdown = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown-id' }));
expect(await dropdown.getValueText()).toBe('Choose one...');
expect(await widget.field.value).toBe('empty');
const dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty');
});
});
@@ -243,7 +237,7 @@ describe('DropdownWidgetComponent', () => {
id: 'dropdown-id',
name: 'date-name',
type: 'dropdown',
readOnly: false,
readOnly: 'false',
restUrl: 'fake-rest-url',
optionType: 'rest'
});
@@ -270,8 +264,9 @@ describe('DropdownWidgetComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
const dropdown = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown-id' }));
expect(await dropdown.getValueText()).toBe('option_2');
const dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('option_2');
expect(dropDownElement.attributes['ng-reflect-model'].textContent).toBe('option_2');
});
it('should select the empty value when no default is chosen', async () => {
@@ -279,9 +274,8 @@ describe('DropdownWidgetComponent', () => {
widget.ngOnInit();
await (await loader.getHarness(MatSelectHarness)).open();
const dropdown = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown-id' }));
expect(await dropdown.getValueText()).toBe('Choose one...');
expect(await widget.field.value).toBe('empty');
const dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty');
});
it('should be disabled when the field is readonly', async () => {
@@ -289,10 +283,9 @@ describe('DropdownWidgetComponent', () => {
id: 'dropdown-id',
name: 'date-name',
type: 'dropdown',
readOnly: true,
readOnly: 'true',
restUrl: 'fake-rest-url'
});
widget.ngOnInit();
fixture.detectChanges();
await fixture.whenStable();
@@ -311,13 +304,10 @@ describe('DropdownWidgetComponent', () => {
readOnly: true,
params: { field: { name: 'date-name', type: 'dropdown' } }
});
widget.ngOnInit();
fixture.detectChanges();
await fixture.whenStable();
const select = await loader.getHarness(MatSelectHarness);
const dropdown = await loader.getHarness(MatSelectHarness);
expect(await dropdown.getValueText()).toEqual('FakeValue');
expect(await select.getValueText()).toEqual('FakeValue');
});
});
});

View File

@@ -17,21 +17,20 @@
/* eslint-disable @angular-eslint/component-selector */
import { Component, inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { FormService, FormFieldOption, WidgetComponent, ErrorWidgetComponent, ErrorMessageModel, FormFieldModel } from '@alfresco/adf-core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { FormService, FormFieldOption, WidgetComponent, ErrorWidgetComponent } from '@alfresco/adf-core';
import { ProcessDefinitionService } from '../../services/process-definition.service';
import { TaskFormService } from '../../services/task-form.service';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { AbstractControl, FormControl, ReactiveFormsModule, ValidationErrors, ValidatorFn } from '@angular/forms';
import { filter, Subject, takeUntil } from 'rxjs';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'dropdown-widget',
standalone: true,
imports: [CommonModule, TranslateModule, MatFormFieldModule, MatSelectModule, ReactiveFormsModule, ErrorWidgetComponent],
imports: [CommonModule, TranslateModule, MatFormFieldModule, MatSelectModule, FormsModule, ErrorWidgetComponent],
templateUrl: './dropdown.widget.html',
styleUrls: ['./dropdown.widget.scss'],
host: {
@@ -47,50 +46,19 @@ import { filter, Subject, takeUntil } from 'rxjs';
},
encapsulation: ViewEncapsulation.None
})
export class DropdownWidgetComponent extends WidgetComponent implements OnInit, OnDestroy {
public formsService = inject(FormService);
public taskFormService = inject(TaskFormService);
public processDefinitionService = inject(ProcessDefinitionService);
dropdownControl = new FormControl<FormFieldOption | string>(undefined);
private readonly onDestroy$ = new Subject<void>();
get isReadOnlyType(): boolean {
return this.field.type === 'readonly';
}
get isReadOnlyField(): boolean {
return this.field.readOnly;
}
private get isRestType(): boolean {
return this.field?.optionType === 'rest';
}
private get hasRestUrl(): boolean {
return !!this.field?.restUrl;
}
private get isValidRestConfig(): boolean {
return this.isRestType && this.hasRestUrl;
export class DropdownWidgetComponent extends WidgetComponent implements OnInit {
constructor(public formService: FormService, public taskFormService: TaskFormService, public processDefinitionService: ProcessDefinitionService) {
super(formService);
}
ngOnInit() {
if (this.isValidRestConfig && !this.isReadOnlyForm()) {
if (this.isValidRestConfig() && !this.isReadOnlyForm()) {
if (this.field.form.taskId) {
this.getValuesByTaskId();
} else {
this.getValuesByProcessDefinitionId();
}
}
this.initFormControl();
}
ngOnDestroy(): void {
this.onDestroy$.next();
this.onDestroy$.complete();
}
getValuesByTaskId() {
@@ -117,84 +85,41 @@ export class DropdownWidgetComponent extends WidgetComponent implements OnInit,
});
}
getOptionValue(option: FormFieldOption, fieldValue: string): string {
let optionValue: string = '';
if (option.id === 'empty' || option.name !== fieldValue) {
optionValue = option.id;
} else {
optionValue = option.name;
}
return optionValue;
}
isReadOnlyType(): boolean {
return this.field.type === 'readonly';
}
showRequiredMessage(): boolean {
return (this.isInvalidFieldRequired() || this.field.value === 'empty') && this.isTouched();
}
get isReadOnlyField(): boolean {
return this.field.readOnly;
}
private isRestType(): boolean {
return this.field?.optionType === 'rest';
}
private isReadOnlyForm(): boolean {
return !!this.field?.form?.readOnly;
}
private initFormControl() {
if (this.field?.required) {
this.dropdownControl.addValidators([this.customRequiredValidator(this.field)]);
}
if (this.field?.readOnly || this.readOnly) {
this.dropdownControl.disable({ emitEvent: false });
}
this.dropdownControl.valueChanges
.pipe(
filter(() => !!this.field),
takeUntil(this.onDestroy$)
)
.subscribe((value) => {
this.setOptionValue(value, this.field);
this.handleErrors();
this.onFieldChanged(this.field);
});
this.dropdownControl.setValue(this.getOptionValue(this.field?.value), { emitEvent: false });
this.handleErrors();
private hasRestUrl(): boolean {
return !!this.field?.restUrl;
}
private handleErrors() {
if (!this.field) {
return;
}
if (this.dropdownControl.valid) {
this.field.validationSummary = new ErrorMessageModel('');
return;
}
if (this.dropdownControl.invalid && this.dropdownControl.errors.required) {
this.field.validationSummary = new ErrorMessageModel({ message: 'FORM.FIELD.REQUIRED' });
}
}
private setOptionValue(option: string | FormFieldOption, field: FormFieldModel) {
if (typeof option === 'string') {
field.value = option;
return;
}
if (option.id === 'empty' || option.name !== field.value) {
field.value = option.id;
return;
}
field.value = option.name;
}
private getOptionValue(value?: string | FormFieldOption) {
if (this.field?.readOnly || this.readOnly) {
return value;
}
if (typeof value === 'string') {
return this.field.options.find((option) => option.id === value || option.name === value);
}
return value as FormFieldOption | undefined;
}
private customRequiredValidator(field: FormFieldModel): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const isEmptyInputValue = (value: any) => value == null || ((typeof value === 'string' || Array.isArray(value)) && value.length === 0);
const isEqualToEmptyValue = (value: any) =>
field.hasEmptyValue &&
(value === field.emptyOption.id ||
value === field.emptyOption.name ||
(value.id === field.emptyOption.id && value.name === field.emptyOption.name));
return isEmptyInputValue(control.value) || isEqualToEmptyValue(control.value) ? { required: true } : null;
};
private isValidRestConfig(): boolean {
return this.isRestType() && this.hasRestUrl();
}
}