From 34acdd20cfbf50c52c289889cd70e2729247bc1d Mon Sep 17 00:00:00 2001 From: Alex Molodyh <140214274+amolodyh-hyland@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:38:20 -0700 Subject: [PATCH] Revert "AAE-46246 Format typed values in form display widgets (#11935)" (#11951) This reverts commit f39b9167b8113f19b38ce907d4744de49fc14970. --- .../base-display-text.widget.ts | 26 +- .../display-text/display-text.widget.spec.ts | 115 -------- .../display-text/display-text.widget.ts | 6 +- .../multiline-text/multiline-text.widget.html | 6 +- .../multiline-text.widget.spec.ts | 75 ------ .../multiline-text/multiline-text.widget.ts | 40 --- .../components/widgets/text/text.widget.html | 5 +- .../widgets/text/text.widget.spec.ts | 113 -------- .../components/widgets/text/text.widget.ts | 40 --- lib/core/src/lib/form/public-api.ts | 2 - .../services/form-expression.service.spec.ts | 65 ----- .../form/services/form-expression.service.ts | 79 ++---- ...form-field-value-formatter.service.spec.ts | 255 ------------------ .../form-field-value-formatter.service.ts | 132 --------- .../form-field-value-formatter.token.ts | 21 -- .../display-external-property.widget.spec.ts | 68 +---- .../display-external-property.widget.ts | 32 +-- .../dropdown/dropdown-cloud.widget.html | 2 +- .../dropdown/dropdown-cloud.widget.spec.ts | 99 +------ .../widgets/dropdown/dropdown-cloud.widget.ts | 29 +- 20 files changed, 36 insertions(+), 1174 deletions(-) delete mode 100644 lib/core/src/lib/form/services/form-field-value-formatter.service.spec.ts delete mode 100644 lib/core/src/lib/form/services/form-field-value-formatter.service.ts delete mode 100644 lib/core/src/lib/form/services/form-field-value-formatter.token.ts diff --git a/lib/core/src/lib/form/components/widgets/base-display-text/base-display-text.widget.ts b/lib/core/src/lib/form/components/widgets/base-display-text/base-display-text.widget.ts index 5528a4633b..b3a6e93bea 100644 --- a/lib/core/src/lib/form/components/widgets/base-display-text/base-display-text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/base-display-text/base-display-text.widget.ts @@ -15,12 +15,10 @@ * limitations under the License. */ -import { ChangeDetectorRef, Component, inject, AfterViewInit, OnInit, DestroyRef, InjectionToken } from '@angular/core'; +import { ChangeDetectorRef, Component, inject, AfterViewInit, DestroyRef, InjectionToken } from '@angular/core'; import { debounceTime, filter, isObservable, Observable } from 'rxjs'; import { FormRulesEvent } from '../../../events'; import { FormExpressionService } from '../../../services/form-expression.service'; -import { FormFieldValueFormatterService } from '../../../services/form-field-value-formatter.service'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field-value-formatter.token'; import { WidgetComponent } from '../widget.component'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -35,14 +33,11 @@ export const ADF_DISPLAY_TEXT_SETTINGS = new InjectionToken | DisplayTextWidgetSettings>(ADF_DISPLAY_TEXT_SETTINGS, { optional: true @@ -50,13 +45,6 @@ export abstract class BaseDisplayTextWidgetComponent extends WidgetComponent imp constructor() { super(); - if (isObservable(this.formattingEnabledToken)) { - this.formattingEnabledToken.pipe(takeUntilDestroyed()).subscribe((enabled: boolean) => { - this.formattingEnabled = enabled ?? false; - }); - } else { - this.formattingEnabled = this.formattingEnabledToken ?? false; - } if (isObservable(this.settings)) { this.settings.pipe(takeUntilDestroyed()).subscribe((data: DisplayTextWidgetSettings) => { this.updateSettingsBasedProperties(data); @@ -66,16 +54,6 @@ export abstract class BaseDisplayTextWidgetComponent extends WidgetComponent imp } } - ngOnInit() { - const value = this.field?.value; - const isFormattableValue = value != null && typeof value !== 'string'; - const shouldFormatValue = this.formattingEnabled && isFormattableValue && this.formatter.hasFormatter(this.field.type); - - if (shouldFormatValue) { - this.field.value = this.formatter.format(this.field); - } - } - override ngAfterViewInit() { if (this.enableExpressionEvaluation) { this.storeOriginalValue(); diff --git a/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.spec.ts b/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.spec.ts index 94dec1370c..30fc30a7a8 100644 --- a/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.spec.ts @@ -17,10 +17,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormFieldModel, FormModel } from '../core'; -import { FormFieldTypes } from '../core/form-field-types'; import { DisplayTextWidgetComponent } from './display-text.widget'; import { ADF_DISPLAY_TEXT_SETTINGS } from '../base-display-text/base-display-text.widget'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field-value-formatter.token'; import { FormService } from '../../../services/form.service'; import { of } from 'rxjs'; @@ -242,117 +240,4 @@ describe('DisplayTextWidgetComponent', () => { }, 100); }); }); - - describe('typed value formatting', () => { - describe('when flag is on', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [DisplayTextWidgetComponent], - providers: [ - FormService, - { provide: ADF_DISPLAY_TEXT_SETTINGS, useValue: { enableExpressionEvaluation: true } }, - { provide: ADF_TYPED_VALUE_FORMATTING_ENABLED, useValue: true } - ] - }); - fixture = TestBed.createComponent(DisplayTextWidgetComponent); - widget = fixture.componentInstance; - }); - - it('should format a direct People value to full name', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'f1', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alyssa', lastName: 'Adcock' }] - }); - fixture.detectChanges(); - - expect(widget.field.value).toBe('Alyssa Adcock'); - }); - - it('should format a direct Group value to group name', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'f1', - type: FormFieldTypes.FUNCTIONAL_GROUP, - value: [{ id: 'g1', name: 'Engineering' }] - }); - fixture.detectChanges(); - - expect(widget.field.value).toBe('Engineering'); - }); - - it('should not contain [object Object] for a complex value', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'f1', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alice', lastName: 'Brown' }] - }); - fixture.detectChanges(); - - expect(String(widget.field.value)).not.toContain('[object Object]'); - }); - - it('should resolve expression templates for People via FormExpressionService', () => { - const form = new FormModel({ - fields: [ - { id: 'displayText1', type: 'display-text', value: 'Selected: ${field.peopleField}' }, - { id: 'peopleField', type: FormFieldTypes.PEOPLE, value: [{ firstName: 'Alyssa', lastName: 'Adcock' }] } - ] - }); - - widget.field = form.getFieldById('displayText1'); - fixture.detectChanges(); - - expect(widget.field.value).toBe('Selected: Alyssa Adcock'); - }); - - it('should re-evaluate expression with formatted value when dependent People field changes', (done) => { - const form = new FormModel({ - fields: [ - { id: 'displayText1', type: 'display-text', value: 'Selected: ${field.peopleField}' }, - { id: 'peopleField', type: FormFieldTypes.PEOPLE, value: [{ firstName: 'Alyssa', lastName: 'Adcock' }] } - ] - }); - formService = TestBed.inject(FormService); - - widget.field = form.getFieldById('displayText1'); - const peopleField = form.getFieldById('peopleField'); - fixture.detectChanges(); - - expect(widget.field.value).toBe('Selected: Alyssa Adcock'); - - peopleField.value = [{ firstName: 'Jane', lastName: 'Smith' }]; - formService.formRulesEvent.next({ type: 'fieldValueChanged', field: peopleField } as any); - - setTimeout(() => { - expect(widget.field.value).toBe('Selected: Jane Smith'); - done(); - }, 350); - }); - }); - - describe('when flag is off', () => { - it('should not format a complex field value (default behaviour preserved)', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [DisplayTextWidgetComponent], - providers: [FormService] - }); - fixture = TestBed.createComponent(DisplayTextWidgetComponent); - widget = fixture.componentInstance; - - const rawValue = [{ firstName: 'Alyssa', lastName: 'Adcock' }]; - widget.field = new FormFieldModel(new FormModel(), { - id: 'f1', - type: FormFieldTypes.PEOPLE, - value: rawValue - }); - - // Trigger only ngOnInit (no full render — raw array would crash TranslatePipe without the flag) - widget.ngOnInit(); - - expect(widget.field.value).toEqual(rawValue); - }); - }); - }); }); diff --git a/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts b/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts index aa13a148d8..6cb7c5dc0e 100644 --- a/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts @@ -48,11 +48,7 @@ export class DisplayTextWidgetComponent extends BaseDisplayTextWidgetComponent { protected evaluateExpressions(): void { if (this.field) { - if (this.formattingEnabled && this.field.value !== null && this.field.value !== undefined && typeof this.field.value !== 'string') { - this.field.value = this.formatter.format(this.field); - } else { - this.field.value = this.resolveExpressions(this.field.value); - } + this.field.value = this.resolveExpressions(this.field.value); } } diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html index ccc6a97448..ef5b3d11cc 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.html @@ -19,8 +19,8 @@ rows="3" [id]="field.id" [required]="field.required" - [ngModel]="displayValue" - (ngModelChange)="onValueChange($event)" + [(ngModel)]="field.value" + (ngModelChange)="onFieldChanged(field)" [disabled]="field.readOnly || readOnly" [placeholder]="field.placeholder" [title]="field.tooltip" @@ -29,7 +29,7 @@
- {{ displayValue?.length || 0 }}/{{ field.maxLength }} + {{ field?.value?.length || 0 }}/{{ field.maxLength }}
diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts index 54f84568a7..abb846e264 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.spec.ts @@ -24,7 +24,6 @@ import { MultilineTextWidgetComponentComponent } from './multiline-text.widget'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field-value-formatter.token'; import { of, Subject } from 'rxjs'; describe('MultilineTextWidgetComponentComponent', () => { @@ -299,78 +298,4 @@ describe('MultilineTextWidgetComponentComponent - ADF_CUSTOM_MESSAGE', () => { expect(widget.field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); }); }); - - describe('typed value formatting', () => { - describe('when flag is on', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [MultilineTextWidgetComponentComponent], - providers: [{ provide: ADF_TYPED_VALUE_FORMATTING_ENABLED, useValue: true }] - }); - fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); - widget = fixture.componentInstance; - testingUtils = new UnitTestingUtils(fixture.debugElement); - }); - - it('should return formatted name for a People value in read-only mode', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'people-field', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alice', lastName: 'Brown' }], - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).toBe('Alice Brown'); - }); - - it('should not return [object Object] for a complex field value', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'people-field', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alice', lastName: 'Brown' }], - readOnly: true - }); - fixture.detectChanges(); - - expect(String(widget.displayValue)).not.toContain('[object Object]'); - }); - - it('should pass through plain string values unchanged', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'multiline-id', - type: FormFieldTypes.MULTILINE_TEXT, - value: 'plain text', - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).toBe('plain text'); - }); - }); - - describe('when flag is off', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [MultilineTextWidgetComponentComponent] - }); - fixture = TestBed.createComponent(MultilineTextWidgetComponentComponent); - widget = fixture.componentInstance; - }); - - it('should not format complex field values (default behaviour preserved)', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'people-field', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alice', lastName: 'Brown' }], - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).not.toBe('Alice Brown'); - }); - }); - }); }); diff --git a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts index 3412224974..541706b69a 100644 --- a/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts @@ -25,13 +25,9 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; import { isObservable } from 'rxjs'; -import { filter } from 'rxjs/operators'; -import { FormRulesEvent } from '../../../events/form-rules.event'; import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; import { ErrorWidgetComponent } from '../error/error.component'; import { WidgetComponent } from '../widget.component'; -import { FormFieldValueFormatterService } from '../../../services/form-field-value-formatter.service'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field-value-formatter.token'; @Component({ selector: 'multiline-text-widget', @@ -54,44 +50,8 @@ import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field export class MultilineTextWidgetComponentComponent extends WidgetComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); private readonly enableCustomMessage = inject(ADF_CUSTOM_MESSAGE, { optional: true }); - private readonly formatter = inject(FormFieldValueFormatterService); - private readonly formattingEnabledToken = inject(ADF_TYPED_VALUE_FORMATTING_ENABLED, { optional: true }); - private formattingEnabled = false; - displayValue = ''; - - onValueChange(value: string): void { - this.field.value = value; - this.displayValue = this.computeDisplayValue(); - this.onFieldChanged(this.field); - } - - private computeDisplayValue(): string { - const value = this.field.value; - if (this.formattingEnabled && (this.field.readOnly || this.readOnly) && value !== null && value !== undefined && typeof value !== 'string') { - return this.formatter.format(this.field); - } - return value as string; - } ngOnInit(): void { - if (isObservable(this.formattingEnabledToken)) { - this.formattingEnabledToken.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { - this.formattingEnabled = enabled ?? false; - this.displayValue = this.computeDisplayValue(); - }); - } else { - this.formattingEnabled = this.formattingEnabledToken ?? false; - this.displayValue = this.computeDisplayValue(); - } - - this.formService.formRulesEvent - .pipe( - filter((event: FormRulesEvent) => event?.type === 'fieldValueChanged'), - takeUntilDestroyed(this.destroyRef) - ) - .subscribe(() => { - this.displayValue = this.computeDisplayValue(); - }); if (this.enableCustomMessage != null) { if (isObservable(this.enableCustomMessage)) { this.enableCustomMessage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.html b/lib/core/src/lib/form/components/widgets/text/text.widget.html index 5312d8451d..13e1c165ef 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.html +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.html @@ -18,8 +18,9 @@ type="text" [id]="field.id" [required]="field.required" - [ngModel]="displayValue" - (ngModelChange)="onValueChange($event)" + [value]="field.value" + [(ngModel)]="field.value" + (ngModelChange)="onFieldChanged(field)" [disabled]="field.readOnly || readOnly" [textMask]="{mask: mask, isReversed: isMaskReversed}" [placeholder]="placeholder" diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts b/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts index 369338f6c9..341b6d8d15 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.spec.ts @@ -25,7 +25,6 @@ import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field-value-formatter.token'; import { of, Subject } from 'rxjs'; describe('TextWidgetComponent', () => { @@ -699,116 +698,4 @@ describe('TextWidgetComponent - ADF_CUSTOM_MESSAGE', () => { expect(widget.field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.INVALID_VALUE'); }); }); - - describe('typed value formatting', () => { - describe('when flag is on', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [TextWidgetComponent], - providers: [{ provide: ADF_TYPED_VALUE_FORMATTING_ENABLED, useValue: true }] - }); - fixture = TestBed.createComponent(TextWidgetComponent); - widget = fixture.componentInstance; - testingUtils = new UnitTestingUtils(fixture.debugElement); - }); - - it('should return formatted name for a People value in read-only mode', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'people-field', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alyssa', lastName: 'Adcock' }], - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).toBe('Alyssa Adcock'); - }); - - it('should return comma-separated labels for a multi-select dropdown in read-only mode', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'dropdown-field', - type: FormFieldTypes.DROPDOWN, - value: [ - { id: 'a', name: 'Apple' }, - { id: 'b', name: 'Banana' } - ], - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).toBe('Apple, Banana'); - }); - - it('should not return [object Object] for a complex field value', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'people-field', - type: FormFieldTypes.PEOPLE, - value: { firstName: 'Alice', lastName: 'Brown' }, - readOnly: true - }); - fixture.detectChanges(); - - expect(String(widget.displayValue)).not.toContain('[object Object]'); - }); - - it('should pass through plain string values unchanged', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'text-id', - type: FormFieldTypes.TEXT, - value: 'hello', - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).toBe('hello'); - }); - }); - - describe('when flag is off', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [TextWidgetComponent] - }); - fixture = TestBed.createComponent(TextWidgetComponent); - widget = fixture.componentInstance; - }); - - it('should not format complex field values (default behaviour preserved)', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'people-field', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alyssa', lastName: 'Adcock' }], - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).not.toBe('Alyssa Adcock'); - }); - }); - - describe('when flag emits via observable', () => { - it('should format value after observable emits true', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [TextWidgetComponent], - providers: [{ provide: ADF_TYPED_VALUE_FORMATTING_ENABLED, useValue: of(true) }] - }); - fixture = TestBed.createComponent(TextWidgetComponent); - widget = fixture.componentInstance; - testingUtils = new UnitTestingUtils(fixture.debugElement); - - widget.field = new FormFieldModel(new FormModel(), { - id: 'people-field', - type: FormFieldTypes.PEOPLE, - value: [{ firstName: 'Alyssa', lastName: 'Adcock' }], - readOnly: true - }); - fixture.detectChanges(); - - expect(widget.displayValue).toBe('Alyssa Adcock'); - }); - }); - }); }); diff --git a/lib/core/src/lib/form/components/widgets/text/text.widget.ts b/lib/core/src/lib/form/components/widgets/text/text.widget.ts index 05d4085f9a..ef6ddbb515 100644 --- a/lib/core/src/lib/form/components/widgets/text/text.widget.ts +++ b/lib/core/src/lib/form/components/widgets/text/text.widget.ts @@ -25,14 +25,10 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { TranslatePipe } from '@ngx-translate/core'; import { isObservable } from 'rxjs'; -import { filter } from 'rxjs/operators'; -import { FormRulesEvent } from '../../../events/form-rules.event'; import { ADF_CUSTOM_MESSAGE } from '../core/custom-validation-message.token'; import { ErrorWidgetComponent } from '../error/error.component'; import { WidgetComponent } from '../widget.component'; import { InputMaskDirective } from './text-mask.component'; -import { FormFieldValueFormatterService } from '../../../services/form-field-value-formatter.service'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from '../../../services/form-field-value-formatter.token'; type FieldStatusTemplate = TemplateRef<{ $implicit: WidgetComponent }>; const FIELD_STATUS_TEMPLATE = new InjectionToken('FIELD_STATUS_TEMPLATE'); @@ -78,44 +74,8 @@ export class TextWidgetComponent extends WidgetComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); private readonly enableCustomMessage = inject(ADF_CUSTOM_MESSAGE, { optional: true }); - private readonly formatter = inject(FormFieldValueFormatterService); - private readonly formattingEnabledToken = inject(ADF_TYPED_VALUE_FORMATTING_ENABLED, { optional: true }); - private formattingEnabled = false; - displayValue = ''; - - onValueChange(value: string): void { - this.field.value = value; - this.displayValue = this.computeDisplayValue(); - this.onFieldChanged(this.field); - } - - private computeDisplayValue(): string { - const value = this.field.value; - if (this.formattingEnabled && (this.field.readOnly || this.readOnly) && value !== null && value !== undefined && typeof value !== 'string') { - return this.formatter.format(this.field); - } - return value as string; - } ngOnInit() { - if (isObservable(this.formattingEnabledToken)) { - this.formattingEnabledToken.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { - this.formattingEnabled = enabled ?? false; - this.displayValue = this.computeDisplayValue(); - }); - } else { - this.formattingEnabled = this.formattingEnabledToken ?? false; - this.displayValue = this.computeDisplayValue(); - } - - this.formService.formRulesEvent - .pipe( - filter((event: FormRulesEvent) => event?.type === 'fieldValueChanged'), - takeUntilDestroyed(this.destroyRef) - ) - .subscribe(() => { - this.displayValue = this.computeDisplayValue(); - }); if (this.enableCustomMessage != null) { if (isObservable(this.enableCustomMessage)) { this.enableCustomMessage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { diff --git a/lib/core/src/lib/form/public-api.ts b/lib/core/src/lib/form/public-api.ts index 0b3dc24535..0f9bfe91cf 100644 --- a/lib/core/src/lib/form/public-api.ts +++ b/lib/core/src/lib/form/public-api.ts @@ -30,8 +30,6 @@ export * from './services/form.service'; export * from './services/form-expression.service'; export * from './services/form-validation-service.interface'; export * from './services/widget-visibility.service'; -export * from './services/form-field-value-formatter.service'; -export * from './services/form-field-value-formatter.token'; export * from './pipes'; diff --git a/lib/core/src/lib/form/services/form-expression.service.spec.ts b/lib/core/src/lib/form/services/form-expression.service.spec.ts index 9fc02a2f94..0758ee8d6c 100644 --- a/lib/core/src/lib/form/services/form-expression.service.spec.ts +++ b/lib/core/src/lib/form/services/form-expression.service.spec.ts @@ -18,10 +18,6 @@ import { TestBed } from '@angular/core/testing'; import { FormExpressionService } from './form-expression.service'; import { FormModel } from '../components/widgets/core'; -import { FormFieldValueFormatterService } from './form-field-value-formatter.service'; -import { FormFieldTypes } from '../components/widgets/core/form-field-types'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from './form-field-value-formatter.token'; -import { of } from 'rxjs'; describe('FormExpressionService', () => { let service: FormExpressionService; @@ -273,67 +269,6 @@ describe('FormExpressionService', () => { expect(result).toBe('true'); }); - describe('when ADF_TYPED_VALUE_FORMATTING_ENABLED token is provided as true', () => { - let formattingService: FormExpressionService; - let formatter: FormFieldValueFormatterService; - - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - providers: [FormExpressionService, { provide: ADF_TYPED_VALUE_FORMATTING_ENABLED, useValue: of(true) }] - }); - formattingService = TestBed.inject(FormExpressionService); - formatter = TestBed.inject(FormFieldValueFormatterService); - }); - - it('should format object field values via the registered formatter', () => { - const mockField = { - id: 'peopleField', - type: FormFieldTypes.PEOPLE, - value: { firstName: 'Alyssa', lastName: 'Adcock' } - }; - spyOn(formModel, 'getFieldById').and.returnValue(mockField as any); - spyOn(formatter, 'hasFormatter').and.returnValue(true); - spyOn(formatter, 'formatValue').and.returnValue('Alyssa Adcock'); - - const result = formattingService.resolveExpressions(formModel, '${field.peopleField}'); - - expect(result).toBe('Alyssa Adcock'); - }); - - it('should fall back to JSON.stringify when no formatter is registered', () => { - const mockField = { - id: 'objectField', - type: 'unregistered-type', - value: { a: 1 } - }; - spyOn(formModel, 'getFieldById').and.returnValue(mockField as any); - spyOn(formatter, 'hasFormatter').and.returnValue(false); - - const result = formattingService.resolveExpressions(formModel, '${field.objectField}'); - - expect(result).toBe('{"a":1}'); - }); - }); - - describe('when ADF_TYPED_VALUE_FORMATTING_ENABLED token is not provided', () => { - it('should default to JSON.stringify even if a formatter exists', () => { - const formatter = TestBed.inject(FormFieldValueFormatterService); - const mockField = { - id: 'peopleField', - type: FormFieldTypes.PEOPLE, - value: { firstName: 'Alyssa', lastName: 'Adcock' } - }; - spyOn(formModel, 'getFieldById').and.returnValue(mockField as any); - const hasFormatterSpy = spyOn(formatter, 'hasFormatter'); - - const result = service.resolveExpressions(formModel, '${field.peopleField}'); - - expect(hasFormatterSpy).not.toHaveBeenCalled(); - expect(result).toBe('{"firstName":"Alyssa","lastName":"Adcock"}'); - }); - }); - describe('when escapeHtml is true', () => { let getFieldSpy: jasmine.Spy; diff --git a/lib/core/src/lib/form/services/form-expression.service.ts b/lib/core/src/lib/form/services/form-expression.service.ts index 0e2d619ae3..01faf4669c 100644 --- a/lib/core/src/lib/form/services/form-expression.service.ts +++ b/lib/core/src/lib/form/services/form-expression.service.ts @@ -15,12 +15,8 @@ * limitations under the License. */ -import { inject, Injectable } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { isObservable } from 'rxjs'; +import { Injectable } from '@angular/core'; import { FormModel } from '../components/widgets/core'; -import { FormFieldValueFormatterService } from './form-field-value-formatter.service'; -import { ADF_TYPED_VALUE_FORMATTING_ENABLED } from './form-field-value-formatter.token'; @Injectable({ providedIn: 'root' @@ -31,20 +27,6 @@ export class FormExpressionService { private readonly VARIABLE_PREFIX = 'variable.'; private readonly VARIABLES_REGEX = /(?:field|variable)\.[a-zA-Z_$][a-zA-Z0-9_$]*/g; - private readonly formFieldValueFormatter = inject(FormFieldValueFormatterService); - private readonly formattingEnabledToken = inject(ADF_TYPED_VALUE_FORMATTING_ENABLED, { optional: true }); - private formattingEnabled = false; - - constructor() { - if (isObservable(this.formattingEnabledToken)) { - this.formattingEnabledToken.pipe(takeUntilDestroyed()).subscribe((enabled: boolean) => { - this.formattingEnabled = enabled ?? false; - }); - } else { - this.formattingEnabled = this.formattingEnabledToken ?? false; - } - } - resolveExpressions(form: FormModel, formField: string, escapeHtml?: boolean): string { let result = formField || ''; @@ -55,10 +37,24 @@ export class FormExpressionService { } for (const match of matches) { - const rawResult = this.resolveExpression(form, match); - let expressionResult = this.normalizeExpressionResult(form, match, rawResult); + let expressionResult = this.resolveExpression(form, match); + if (expressionResult === null || expressionResult === undefined) { + expressionResult = ''; + } else if (typeof expressionResult !== 'string') { + expressionResult = JSON.stringify(expressionResult); + } if (escapeHtml) { - expressionResult = this.escapeHtmlEntities(expressionResult); + expressionResult = expressionResult + .split('&') + .join('&') + .split('<') + .join('<') + .split('>') + .join('>') + .split('"') + .join('"') + .split("'") + .join('''); } result = result.replace(match, expressionResult); } @@ -66,37 +62,6 @@ export class FormExpressionService { return result; } - private normalizeExpressionResult(form: FormModel, match: string, expressionResult: any): string { - if (expressionResult == null) { - return ''; - } - - if (typeof expressionResult === 'string') { - return expressionResult; - } - - return this.formatTypedExpressionResult(form, match, expressionResult); - } - - private formatTypedExpressionResult(form: FormModel, match: string, expressionResult: any): string { - if (!this.formattingEnabled) { - return JSON.stringify(expressionResult); - } - - const fieldId = this.extractFieldIdFromMatch(match); - const sourceField = fieldId ? form.getFieldById(fieldId) : undefined; - - if (sourceField && this.formFieldValueFormatter.hasFormatter(sourceField.type)) { - return this.formFieldValueFormatter.formatValue(expressionResult, sourceField); - } - - return JSON.stringify(expressionResult); - } - - private escapeHtmlEntities(value: string): string { - return value.split('&').join('&').split('<').join('<').split('>').join('>').split('"').join('"').split("'").join('''); - } - private resolveExpression(form: FormModel, expression: any): any { if (expression === undefined || expression === null) { return expression; @@ -131,14 +96,6 @@ export class FormExpressionService { } } - private extractFieldIdFromMatch(match: string): string | null { - const inner = match.slice(2, -1).trim(); - if (inner.startsWith(this.FIELD_PREFIX)) { - return inner.slice(this.FIELD_PREFIX.length); - } - return null; - } - getFieldDependencies(expression: string): string[] { const dependencies: string[] = []; const matches = expression.match(this.GLOBAL_EXPRESSION_REGEX); diff --git a/lib/core/src/lib/form/services/form-field-value-formatter.service.spec.ts b/lib/core/src/lib/form/services/form-field-value-formatter.service.spec.ts deleted file mode 100644 index 6cc12bc31b..0000000000 --- a/lib/core/src/lib/form/services/form-field-value-formatter.service.spec.ts +++ /dev/null @@ -1,255 +0,0 @@ -/*! - * @license - * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * 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 { TestBed } from '@angular/core/testing'; -import { FormFieldValueFormatterService } from './form-field-value-formatter.service'; -import { FormFieldModel } from '../components/widgets/core/form-field.model'; -import { FormFieldTypes } from '../components/widgets/core/form-field-types'; -import { FormModel } from '../components/widgets/core'; - -/** - * Test helper that creates a FormFieldModel attached to a fresh FormModel for unit tests. - * - * @param type form field type identifier (e.g., FormFieldTypes.PEOPLE) - * @param value initial field value - * @param options optional dropdown/radio option list - * @returns a fully-wired FormFieldModel - */ -function makeField(type: string, value: any, options?: { id: string; name: string }[]): FormFieldModel { - const form = new FormModel(); - const field = new FormFieldModel(form, { id: 'f1', type, value }); - if (options) { - field.options = options; - } - return field; -} - -describe('FormFieldValueFormatterService', () => { - let service: FormFieldValueFormatterService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(FormFieldValueFormatterService); - }); - - describe('hasFormatter', () => { - it('should return true for registered types', () => { - expect(service.hasFormatter(FormFieldTypes.PEOPLE)).toBeTrue(); - expect(service.hasFormatter(FormFieldTypes.FUNCTIONAL_GROUP)).toBeTrue(); - expect(service.hasFormatter(FormFieldTypes.DROPDOWN)).toBeTrue(); - expect(service.hasFormatter(FormFieldTypes.RADIO_BUTTONS)).toBeTrue(); - }); - - it('should return false for unregistered types', () => { - expect(service.hasFormatter('unknown-type')).toBeFalse(); - expect(service.hasFormatter('text')).toBeFalse(); - }); - }); - - describe('formatValue - base cases', () => { - it('should return empty string for null', () => { - const field = makeField('text', null); - expect(service.formatValue(null, field)).toBe(''); - }); - - it('should return empty string for undefined', () => { - const field = makeField('text', undefined); - expect(service.formatValue(undefined, field)).toBe(''); - }); - - it('should pass through string values unchanged', () => { - const field = makeField('text', 'hello'); - expect(service.formatValue('hello', field)).toBe('hello'); - }); - - it('should JSON.stringify unknown object types', () => { - const field = makeField('text', { foo: 'bar' }); - expect(service.formatValue({ foo: 'bar' }, field)).toBe('{"foo":"bar"}'); - }); - }); - - describe('People formatter', () => { - it('should return empty string for null', () => { - const field = makeField(FormFieldTypes.PEOPLE, null); - expect(service.format(field)).toBe(''); - }); - - it('should return empty string for empty array', () => { - const field = makeField(FormFieldTypes.PEOPLE, []); - expect(service.format(field)).toBe(''); - }); - - it('should format a single user object (single-select mode)', () => { - const field = makeField(FormFieldTypes.PEOPLE, { firstName: 'Alyssa', lastName: 'Adcock' }); - expect(service.format(field)).toBe('Alyssa Adcock'); - }); - - it('should format an array with one user', () => { - const field = makeField(FormFieldTypes.PEOPLE, [{ firstName: 'Alyssa', lastName: 'Adcock' }]); - expect(service.format(field)).toBe('Alyssa Adcock'); - }); - - it('should format multiple users separated by comma', () => { - const field = makeField(FormFieldTypes.PEOPLE, [ - { firstName: 'Alice', lastName: 'Brown' }, - { firstName: 'Bob', lastName: 'Smith' } - ]); - expect(service.format(field)).toBe('Alice Brown, Bob Smith'); - }); - - it('should fall back to username when no first/last name', () => { - const field = makeField(FormFieldTypes.PEOPLE, [{ username: 'jdoe' }]); - expect(service.format(field)).toBe('jdoe'); - }); - - it('should fall back to email when no first/last/username', () => { - const field = makeField(FormFieldTypes.PEOPLE, [{ email: 'a@b.com' }]); - expect(service.format(field)).toBe('a@b.com'); - }); - }); - - describe('Group formatter', () => { - it('should return empty string for null', () => { - const field = makeField(FormFieldTypes.FUNCTIONAL_GROUP, null); - expect(service.format(field)).toBe(''); - }); - - it('should return empty string for empty array', () => { - const field = makeField(FormFieldTypes.FUNCTIONAL_GROUP, []); - expect(service.format(field)).toBe(''); - }); - - it('should format a single group object', () => { - const field = makeField(FormFieldTypes.FUNCTIONAL_GROUP, { id: 'g1', name: 'Engineering' }); - expect(service.format(field)).toBe('Engineering'); - }); - - it('should format an array of groups', () => { - const field = makeField(FormFieldTypes.FUNCTIONAL_GROUP, [{ name: 'Eng' }, { name: 'QA' }]); - expect(service.format(field)).toBe('Eng, QA'); - }); - - it('should skip groups with no name', () => { - const field = makeField(FormFieldTypes.FUNCTIONAL_GROUP, [{ id: 'g1' }, { name: 'QA' }]); - expect(service.format(field)).toBe('QA'); - }); - }); - - describe('Dropdown formatter', () => { - const options = [ - { id: 'a', name: 'Apple' }, - { id: 'b', name: 'Banana' } - ]; - - it('should return empty string for null', () => { - const field = makeField(FormFieldTypes.DROPDOWN, null, options); - expect(service.format(field)).toBe(''); - }); - - it('should return empty string for empty string', () => { - const field = makeField(FormFieldTypes.DROPDOWN, '', options); - expect(service.formatValue('', field)).toBe(''); - }); - - it('Shape A: should look up label for string id', () => { - const field = makeField(FormFieldTypes.DROPDOWN, 'a', options); - expect(service.format(field)).toBe('Apple'); - }); - - it('Shape A fallback: should return the id when not found in options', () => { - const field = makeField(FormFieldTypes.DROPDOWN, 'unknown', options); - expect(service.format(field)).toBe('unknown'); - }); - - it('Shape B: should use .name from object value', () => { - const field = makeField(FormFieldTypes.DROPDOWN, { id: 'a', name: 'Apple' }, options); - expect(service.format(field)).toBe('Apple'); - }); - - it('Shape B no name: should JSON.stringify', () => { - const field = makeField(FormFieldTypes.DROPDOWN, { id: 'a' }, options); - expect(service.format(field)).toBe('{"id":"a"}'); - }); - - it('Shape C: should format array of objects', () => { - const field = makeField( - FormFieldTypes.DROPDOWN, - [ - { id: 'a', name: 'Apple' }, - { id: 'b', name: 'Banana' } - ], - options - ); - expect(service.format(field)).toBe('Apple, Banana'); - }); - - it('Shape C string array: should look up labels for each id', () => { - const field = makeField(FormFieldTypes.DROPDOWN, ['a', 'b'], options); - expect(service.format(field)).toBe('Apple, Banana'); - }); - - it('Shape C empty array: should return empty string', () => { - const field = makeField(FormFieldTypes.DROPDOWN, [], options); - expect(service.format(field)).toBe(''); - }); - }); - - describe('Radio formatter', () => { - const options = [ - { id: 'yes', name: 'Yes' }, - { id: 'no', name: 'No' } - ]; - - it('should return empty string for null', () => { - const field = makeField(FormFieldTypes.RADIO_BUTTONS, null, options); - expect(service.format(field)).toBe(''); - }); - - it('should return empty string for empty string', () => { - const field = makeField(FormFieldTypes.RADIO_BUTTONS, '', options); - expect(service.formatValue('', field)).toBe(''); - }); - - it('Shape A: should look up label for string id', () => { - const field = makeField(FormFieldTypes.RADIO_BUTTONS, 'yes', options); - expect(service.format(field)).toBe('Yes'); - }); - - it('Shape A fallback: should return the id when not found', () => { - const field = makeField(FormFieldTypes.RADIO_BUTTONS, 'unknown', options); - expect(service.format(field)).toBe('unknown'); - }); - - it('Shape B: should use .name from FormFieldOption object (post-click value)', () => { - const field = makeField(FormFieldTypes.RADIO_BUTTONS, { id: 'yes', name: 'Yes' }, options); - expect(service.format(field)).toBe('Yes'); - }); - - it('Shape B no name: should JSON.stringify', () => { - const field = makeField(FormFieldTypes.RADIO_BUTTONS, { id: 'yes' }, options); - expect(service.format(field)).toBe('{"id":"yes"}'); - }); - }); - - describe('register', () => { - it('should allow overriding a registered formatter', () => { - service.register(FormFieldTypes.PEOPLE, () => 'custom-override'); - const field = makeField(FormFieldTypes.PEOPLE, [{ firstName: 'Alice' }]); - expect(service.format(field)).toBe('custom-override'); - }); - }); -}); diff --git a/lib/core/src/lib/form/services/form-field-value-formatter.service.ts b/lib/core/src/lib/form/services/form-field-value-formatter.service.ts deleted file mode 100644 index aaf9eb9101..0000000000 --- a/lib/core/src/lib/form/services/form-field-value-formatter.service.ts +++ /dev/null @@ -1,132 +0,0 @@ -/*! - * @license - * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * 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 { Injectable } from '@angular/core'; -import { FormFieldModel } from '../components/widgets/core/form-field.model'; -import { FormFieldTypes } from '../components/widgets/core/form-field-types'; -import { FullNamePipe } from '../../pipes/full-name.pipe'; - -export type FormFieldValueFormatter = (value: any, field: FormFieldModel) => string; - -@Injectable({ providedIn: 'root' }) -export class FormFieldValueFormatterService { - private readonly formatters = new Map(); - private readonly fullNamePipe = new FullNamePipe(); - - constructor() { - this.register(FormFieldTypes.PEOPLE, (value) => this.formatPeople(value)); - this.register(FormFieldTypes.FUNCTIONAL_GROUP, (value) => this.formatGroup(value)); - this.register(FormFieldTypes.DROPDOWN, (value, field) => this.formatDropdown(value, field)); - this.register(FormFieldTypes.RADIO_BUTTONS, (value, field) => this.formatRadio(value, field)); - } - - register(fieldType: string, formatter: FormFieldValueFormatter): void { - this.formatters.set(fieldType, formatter); - } - - hasFormatter(fieldType: string): boolean { - return this.formatters.has(fieldType); - } - - format(field: FormFieldModel): string { - return this.formatValue(field.value, field); - } - - formatValue(value: any, field: FormFieldModel): string { - if (value === null || value === undefined) { - return ''; - } - const formatter = this.formatters.get(field.type); - if (formatter) { - return formatter(value, field); - } - if (typeof value === 'string') { - return value; - } - return JSON.stringify(value); - } - - private formatPeople(value: any): string { - if (!value) { - return ''; - } - const users = Array.isArray(value) ? value : [value]; - if (users.length === 0) { - return ''; - } - return users - .map((u) => this.fullNamePipe.transform(u)) - .filter((s) => !!s) - .join(', '); - } - - private formatGroup(value: any): string { - if (!value) { - return ''; - } - const groups = Array.isArray(value) ? value : [value]; - if (groups.length === 0) { - return ''; - } - return groups - .map((g) => g?.name ?? '') - .filter((s) => !!s) - .join(', '); - } - - private formatDropdown(value: any, field: FormFieldModel): string { - if (value === null || value === undefined) { - return ''; - } - if (typeof value === 'string') { - if (value === '') { - return ''; - } - const option = field.options?.find((o) => o.id === value); - return option?.name ?? value; - } - if (Array.isArray(value)) { - if (value.length === 0) { - return ''; - } - return value - .map((v) => { - if (typeof v === 'string') { - return field.options?.find((o) => o.id === v)?.name ?? v; - } - return v?.name ?? ''; - }) - .filter((s) => !!s) - .join(', '); - } - return value?.name ?? JSON.stringify(value); - } - - private formatRadio(value: any, field: FormFieldModel): string { - if (value === null || value === undefined) { - return ''; - } - if (typeof value === 'string') { - if (value === '') { - return ''; - } - const option = field.options?.find((o) => o.id === value); - return option?.name ?? value; - } - return value?.name ?? JSON.stringify(value); - } -} diff --git a/lib/core/src/lib/form/services/form-field-value-formatter.token.ts b/lib/core/src/lib/form/services/form-field-value-formatter.token.ts deleted file mode 100644 index 17b4de2857..0000000000 --- a/lib/core/src/lib/form/services/form-field-value-formatter.token.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*! - * @license - * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * 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 { InjectionToken } from '@angular/core'; -import { Observable } from 'rxjs'; - -export const ADF_TYPED_VALUE_FORMATTING_ENABLED = new InjectionToken | boolean>('adf-typed-value-formatting-enabled'); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.spec.ts index 0183662e75..8a838753c1 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FormFieldModel, FormModel, FormFieldTypes, UnitTestingUtils, ADF_TYPED_VALUE_FORMATTING_ENABLED } from '@alfresco/adf-core'; +import { FormFieldModel, FormModel, FormFieldTypes, UnitTestingUtils } from '@alfresco/adf-core'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { ComponentFixture, TestBed } from '@angular/core/testing'; @@ -207,70 +207,4 @@ describe('DisplayExternalPropertyWidgetComponent', () => { expect(adfLeftLabel).toBeNull(); }); }); - - describe('typed value formatting', () => { - describe('when flag is on', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [DisplayExternalPropertyWidgetComponent], - providers: [{ provide: ADF_TYPED_VALUE_FORMATTING_ENABLED, useValue: true }] - }); - fixture = TestBed.createComponent(DisplayExternalPropertyWidgetComponent); - widget = fixture.componentInstance; - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('should display formatted full name for a People value', async () => { - widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { - type: FormFieldTypes.PEOPLE, - readOnly: true, - value: [{ firstName: 'Alyssa', lastName: 'Adcock' }] - }); - fixture.detectChanges(); - - const input = await loader.getHarness(MatInputHarness); - expect(await input.getValue()).toBe('Alyssa Adcock'); - }); - - it('should display comma-separated group names for a Group value', async () => { - widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { - type: FormFieldTypes.FUNCTIONAL_GROUP, - readOnly: true, - value: [{ name: 'Eng' }, { name: 'QA' }] - }); - fixture.detectChanges(); - - const input = await loader.getHarness(MatInputHarness); - expect(await input.getValue()).toBe('Eng, QA'); - }); - - it('should not contain [object Object] for a complex value', async () => { - widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { - type: FormFieldTypes.PEOPLE, - readOnly: true, - value: [{ firstName: 'Alice', lastName: 'Brown' }] - }); - fixture.detectChanges(); - - const input = await loader.getHarness(MatInputHarness); - expect(await input.getValue()).not.toContain('[object Object]'); - }); - }); - - describe('when flag is off', () => { - it('should leave raw string value unchanged (default behaviour preserved)', async () => { - widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { - type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY, - readOnly: true, - externalProperty: 'prop', - value: 'banana' - }); - fixture.detectChanges(); - - const input = await loader.getHarness(MatInputHarness); - expect(await input.getValue()).toBe('banana'); - }); - }); - }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.ts index 8207a86d5d..b159394bb8 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-external-property/display-external-property.widget.ts @@ -15,16 +15,14 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; -import { WidgetComponent, FormBaseModule, FormFieldValueFormatterService, ADF_TYPED_VALUE_FORMATTING_ENABLED } from '@alfresco/adf-core'; +import { ChangeDetectionStrategy, Component, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { WidgetComponent, FormBaseModule } from '@alfresco/adf-core'; import { CommonModule } from '@angular/common'; import { TranslatePipe } from '@ngx-translate/core'; import { FormCloudService } from '../../../services/form-cloud.service'; import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { isObservable } from 'rxjs'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ standalone: true, @@ -52,40 +50,18 @@ export class DisplayExternalPropertyWidgetComponent extends WidgetComponent impl propertyControl: FormControl; private readonly formCloudService = inject(FormCloudService); - private readonly formatter = inject(FormFieldValueFormatterService); - private readonly formattingEnabledToken = inject(ADF_TYPED_VALUE_FORMATTING_ENABLED, { optional: true }); - private readonly destroyRef = inject(DestroyRef); - private formattingEnabled = false; ngOnInit(): void { - if (isObservable(this.formattingEnabledToken)) { - this.formattingEnabledToken.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { - this.formattingEnabled = enabled ?? false; - if (this.propertyControl) { - this.propertyControl.setValue(this.computeDisplayValue()); - } - }); - } else { - this.formattingEnabled = this.formattingEnabledToken ?? false; - } this.initFormControl(); this.initPreviewState(); this.handleFailedPropertyLoad(); } - private computeDisplayValue(): unknown { - const value = this.field?.value; - if (this.formattingEnabled && value !== null && value !== undefined && typeof value !== 'string') { - return this.formatter.format(this.field); - } - return value; - } - private initFormControl(): void { this.propertyControl = new FormControl( { - value: this.computeDisplayValue(), - disabled: !!(this.field?.readOnly || this.readOnly) + value: this.field?.value, + disabled: this.field?.readOnly || this.readOnly }, this.isRequired() ? [Validators.required] : [] ); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html index c84fca1b7c..dc3c0d227e 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.html @@ -43,7 +43,7 @@ [id]="'readonlyOption-' + field.id" [value]="field.value" > - {{readOnlyDisplayValue}} + {{field.value}} } diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts index ac8beec0b0..aef71b54bd 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts @@ -27,8 +27,7 @@ import { FormFieldTypes, UnitTestingUtils, FormFieldComponent, - FormRenderingService, - ADF_TYPED_VALUE_FORMATTING_ENABLED + FormRenderingService } from '@alfresco/adf-core'; import { FormCloudService } from '../../../services/form-cloud.service'; import { @@ -1516,100 +1515,4 @@ describe('DropdownCloudWidgetComponent instantiated by FormFieldComponent wrappe expect(selectedOption).toEqual('option1'); expect(setValueSpy).toHaveBeenCalledTimes(1); }); - - describe('typed value formatting (readOnlyDisplayValue)', () => { - const options = [ - { id: 'a', name: 'Apple' }, - { id: 'b', name: 'Banana' } - ]; - - let fixture: ComponentFixture; - let widget: DropdownCloudWidgetComponent; - - describe('when flag is on', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [DropdownCloudWidgetComponent], - providers: [{ provide: ADF_TYPED_VALUE_FORMATTING_ENABLED, useValue: true }] - }); - fixture = TestBed.createComponent(DropdownCloudWidgetComponent); - widget = fixture.componentInstance; - }); - - it('should return formatted label for an object value', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'dropdown-field', - type: FormFieldTypes.DROPDOWN, - value: { id: 'a', name: 'Apple' } - }); - widget.field.options = options; - fixture.detectChanges(); - - expect(widget.readOnlyDisplayValue).toBe('Apple'); - }); - - it('should return comma-separated labels for an array value', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'dropdown-field', - type: FormFieldTypes.DROPDOWN, - value: [ - { id: 'a', name: 'Apple' }, - { id: 'b', name: 'Banana' } - ] - }); - widget.field.options = options; - fixture.detectChanges(); - - expect(widget.readOnlyDisplayValue).toBe('Apple, Banana'); - }); - - it('should not contain [object Object] for an object value', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'dropdown-field', - type: FormFieldTypes.DROPDOWN, - value: { id: 'a', name: 'Apple' } - }); - widget.field.options = options; - fixture.detectChanges(); - - expect(widget.readOnlyDisplayValue).not.toContain('[object Object]'); - }); - - it('should pass through a plain string value unchanged', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'dropdown-field', - type: FormFieldTypes.DROPDOWN, - value: 'a' - }); - widget.field.options = options; - fixture.detectChanges(); - - expect(widget.readOnlyDisplayValue).toBe('Apple'); - }); - }); - - describe('when flag is off', () => { - beforeEach(() => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [DropdownCloudWidgetComponent] - }); - fixture = TestBed.createComponent(DropdownCloudWidgetComponent); - widget = fixture.componentInstance; - }); - - it('should return the raw field value (default behaviour preserved)', () => { - widget.field = new FormFieldModel(new FormModel(), { - id: 'dropdown-field', - type: FormFieldTypes.DROPDOWN, - value: { id: 'a', name: 'Apple' } - }); - widget.field.options = options; - fixture.detectChanges(); - - expect(widget.readOnlyDisplayValue).not.toBe('Apple'); - }); - }); - }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts index 02795e73e6..682a6391b9 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts @@ -23,8 +23,6 @@ import { FormFieldModel, FormFieldOption, FormFieldTypes, - FormFieldValueFormatterService, - ADF_TYPED_VALUE_FORMATTING_ENABLED, FormService, ReactiveFormWidget, RuleEntry, @@ -33,15 +31,15 @@ import { } from '@alfresco/adf-core'; import { AsyncPipe, NgClass } from '@angular/common'; import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { TranslatePipe } from '@ngx-translate/core'; -import { BehaviorSubject, isObservable, Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { debounceTime, filter, map } from 'rxjs/operators'; import { TaskVariableCloud } from '../../../models/task-variable-cloud.model'; import { FormCloudService } from '../../../services/form-cloud.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormUtilsService } from '../../../services/form-utils.service'; import { defaultValueValidator } from './validators'; @@ -79,10 +77,6 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI private readonly appConfig = inject(AppConfigService); private readonly formUtilsService = inject(FormUtilsService); private readonly destroyRef = inject(DestroyRef); - private readonly formatter = inject(FormFieldValueFormatterService); - private readonly formattingEnabledToken = inject(ADF_TYPED_VALUE_FORMATTING_ENABLED, { optional: true }); - private formattingEnabled = false; - readOnlyDisplayValue: string | undefined; typeId = 'DropdownCloudWidgetComponent'; showInputFilter = false; @@ -138,16 +132,6 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI } ngOnInit() { - if (isObservable(this.formattingEnabledToken)) { - this.formattingEnabledToken.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((enabled: boolean) => { - this.formattingEnabled = enabled ?? false; - this.readOnlyDisplayValue = this.computeReadOnlyDisplayValue(); - }); - } else { - this.formattingEnabled = this.formattingEnabledToken ?? false; - this.readOnlyDisplayValue = this.computeReadOnlyDisplayValue(); - } - /* We can have a lot of 'control.setValue' caused by form rules events e.g. every time if we focusin/focusout etc. we are calling a setValue. @@ -177,17 +161,8 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI }); } - private computeReadOnlyDisplayValue(): string | undefined { - const value = this.field.value; - const isFormattableValue = value != null && (typeof value !== 'string' || this.formatter.hasFormatter(this.field.type)); - const shouldFormatValue = this.formattingEnabled && isFormattableValue; - - return shouldFormatValue ? this.formatter.format(this.field) : value; - } - updateReactiveFormControl(): void { this.setFormControlValue(); - this.readOnlyDisplayValue = this.computeReadOnlyDisplayValue(); this.updateFormControlState(); if (this.field?.form?.showAllValidationErrors) {