mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-46246 Format typed values in form display widgets (#11935)
This commit is contained in:
+67
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FormFieldModel, FormModel, FormFieldTypes, UnitTestingUtils } from '@alfresco/adf-core';
|
||||
import { FormFieldModel, FormModel, FormFieldTypes, UnitTestingUtils, ADF_TYPED_VALUE_FORMATTING_ENABLED } 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,4 +207,70 @@ 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: '<id>' }), {
|
||||
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: '<id>' }), {
|
||||
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: '<id>' }), {
|
||||
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: '<id>' }), {
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+28
-4
@@ -15,14 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeDetectionStrategy, Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { WidgetComponent, FormBaseModule } from '@alfresco/adf-core';
|
||||
import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { WidgetComponent, FormBaseModule, FormFieldValueFormatterService, ADF_TYPED_VALUE_FORMATTING_ENABLED } 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,
|
||||
@@ -50,18 +52,40 @@ 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.field?.value,
|
||||
disabled: this.field?.readOnly || this.readOnly
|
||||
value: this.computeDisplayValue(),
|
||||
disabled: !!(this.field?.readOnly || this.readOnly)
|
||||
},
|
||||
this.isRequired() ? [Validators.required] : []
|
||||
);
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
[id]="'readonlyOption-' + field.id"
|
||||
[value]="field.value"
|
||||
>
|
||||
{{field.value}}
|
||||
{{readOnlyDisplayValue}}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
|
||||
+98
-1
@@ -27,7 +27,8 @@ import {
|
||||
FormFieldTypes,
|
||||
UnitTestingUtils,
|
||||
FormFieldComponent,
|
||||
FormRenderingService
|
||||
FormRenderingService,
|
||||
ADF_TYPED_VALUE_FORMATTING_ENABLED
|
||||
} from '@alfresco/adf-core';
|
||||
import { FormCloudService } from '../../../services/form-cloud.service';
|
||||
import {
|
||||
@@ -1515,4 +1516,100 @@ 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<DropdownCloudWidgetComponent>;
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+27
-2
@@ -23,6 +23,8 @@ import {
|
||||
FormFieldModel,
|
||||
FormFieldOption,
|
||||
FormFieldTypes,
|
||||
FormFieldValueFormatterService,
|
||||
ADF_TYPED_VALUE_FORMATTING_ENABLED,
|
||||
FormService,
|
||||
ReactiveFormWidget,
|
||||
RuleEntry,
|
||||
@@ -31,15 +33,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, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, isObservable, 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';
|
||||
|
||||
@@ -77,6 +79,10 @@ 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;
|
||||
@@ -132,6 +138,16 @@ 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.
|
||||
@@ -161,8 +177,17 @@ 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) {
|
||||
|
||||
Reference in New Issue
Block a user