AAE-30110 Render sections in runtime (#10599)

* AAE-30110 Render sections in runtime

* Move section to separate component

* Improve sections styles

* added early return improvement

* fix process services unit tests

* use testingUtils

* fix styles

* fix lint header error
This commit is contained in:
Tomasz Gnyp 2025-02-12 12:37:17 +01:00 committed by GitHub
parent 287519ee24
commit 2284ede0c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 997 additions and 206 deletions

View File

@ -1,6 +1,5 @@
<div [id]="'field-' + field?.id + '-container'"
[style.visibility]="!field?.isVisible ? 'hidden' : 'visible'"
[style.display]="!field?.isVisible ? 'none' : 'block'"
[style.display]="field?.isVisible ? 'block' : 'none'"
[style]="field | adfFieldStyle"
[class.adf-focus]="focus"
(focusin)="focusToggle()"

View File

@ -120,7 +120,6 @@ describe('FormFieldComponent', () => {
fixture.detectChanges();
const styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles;
expect(styles.visibility).toEqual('hidden');
expect(styles.display).toEqual('none');
});
@ -135,7 +134,6 @@ describe('FormFieldComponent', () => {
fixture.detectChanges();
const styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles;
expect(styles.visibility).toEqual('visible');
expect(styles.display).toEqual('block');
});
@ -147,13 +145,14 @@ describe('FormFieldComponent', () => {
component.field = field;
fixture.detectChanges();
let styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles;
expect(styles.visibility).toEqual('visible');
expect(styles.display).toEqual('block');
component.field.isVisible = false;
fixture.detectChanges();
styles = testingUtils.getByCSS('#field-FAKE-TXT-WIDGET-container').styles;
expect(styles.visibility).toEqual('hidden');
expect(styles.display).toEqual('none');
});

View File

@ -38,15 +38,17 @@
<section class="adf-grid-list-column-view" *ngIf="currentRootElement?.isExpanded">
<div class="adf-grid-list-single-column"
*ngFor="let column of currentRootElement?.columns"
[style.width.%]="getColumnWith(currentRootElement)">
<div class="adf-grid-list-column-view-item" *ngFor="let field of column?.fields">
[style.width.%]="getColumnWidth(currentRootElement)">
<ng-container *ngFor="let field of column?.fields">
<ng-container *ngIf="field.type === 'section'; else formField">
to be implemented
<adf-form-section [field]="field"/>
</ng-container>
<ng-template #formField>
<adf-form-field [field]="field" />
<div class="adf-grid-list-column-view-item">
<adf-form-field [field]="field"/>
</div>
</ng-template>
</div>
</ng-container>
</div>
</section>
</ng-template>

View File

@ -14,39 +14,37 @@
}
.adf-container-widget {
.adf-grid-list-column-view {
@include layout-bp(lt-md) {
display: flow;
}
display: flex;
align-items: flex-start;
margin-right: -1%;
}
.adf-grid-list-single-column {
display: flex;
flex-direction: column;
align-items: flex-start;
flex: 1 1 auto;
}
.adf-grid-list-column-view-item {
width: 100%;
flex-grow: 1;
box-sizing: border-box;
padding-left: 1%;
padding-right: 1%;
}
.adf-grid-list {
display: grid;
}
.adf-grid-list-item {
box-sizing: border-box;
padding-left: 3px;
padding-right: 3px;
&-column-view {
@include layout-bp(lt-md) {
display: flow;
}
display: flex;
margin-right: -1%;
&-item {
width: 100%;
flex-grow: 1;
box-sizing: border-box;
padding-left: 1%;
padding-right: 1%;
}
}
&-single-column {
display: flex;
flex-direction: column;
flex: 1 1 auto;
}
&-item {
box-sizing: border-box;
padding-left: 3px;
padding-right: 3px;
}
}
@include layout-bp(lt-md) {

View File

@ -37,6 +37,7 @@ import {
formNumberTextJson,
formNumberWidgetVisibility,
formRequiredNumberWidget,
mockSectionVisibilityForm,
multilineWidgetFormVisibilityMock,
numberMinMaxForm,
numberNotRequiredForm,
@ -44,22 +45,22 @@ import {
radioWidgetVisibilityForm,
textWidgetVisibility
} from './mock/form-renderer.component.mock';
import { TextWidgetComponent } from './widgets';
import { FormModel, TextWidgetComponent } from './widgets';
const typeIntoInput = (testingUtils: UnitTestingUtils, selector: string, message: string) => {
testingUtils.fillInputByCSS(selector, message);
};
const expectElementToBeHidden = (testingUtils: UnitTestingUtils, selector: string): void => {
const targetElement = testingUtils.getByCSS(selector).nativeElement;
const expectElementToBeHidden = (testingUtils: UnitTestingUtils, fieldId: string): void => {
const targetElement = testingUtils.getByCSS(`#field-${fieldId}-container`).nativeElement;
expect(targetElement).toBeTruthy();
expect(targetElement.style.visibility).toBe('hidden', `${targetElement.id} should be hidden but it is not`);
expect(targetElement.style.display).toBe('none', `${targetElement.id} should be hidden but it is not`);
};
const expectElementToBeVisible = (testingUtils: UnitTestingUtils, selector: string): void => {
const targetElement = testingUtils.getByCSS(selector).nativeElement;
const expectElementToBeVisible = (testingUtils: UnitTestingUtils, fieldId: string, display: string = 'block'): void => {
const targetElement = testingUtils.getByCSS(`#field-${fieldId}-container`).nativeElement;
expect(targetElement).toBeTruthy();
expect(targetElement.style.visibility).not.toBe('hidden', `${targetElement.id} should be visibile but it is not`);
expect(targetElement.style.display).toBe(display, `${targetElement.id} should be visible but it is not`);
};
const expectInputElementValueIs = (testingUtils: UnitTestingUtils, selector: string, value: string): void => {
@ -108,13 +109,13 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Text0pqd1u-container');
expectElementToBeHidden(testingUtils, 'Text0pqd1u');
testingUtils.fillInputByCSS('#Date0hwq20', '2019-11-19');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text0pqd1u-container');
expectElementToBeVisible(testingUtils, 'Text0pqd1u');
});
it('Should not be able to see a widget when the visibility condition refers to another fields with specific date', async () => {
@ -122,14 +123,14 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text0uyqd3-container');
expectElementToBeVisible(testingUtils, 'Text0uyqd3');
testingUtils.fillInputByCSS('#Date0hwq20', '2019-11-19');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Text0uyqd3-container');
expectElementToBeHidden(testingUtils, 'Text0uyqd3');
});
it('[C310336] - Should be able to set visibility conditions for Date widget', async () => {
@ -137,14 +138,14 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text5asd0a-container');
expectElementToBeHidden(testingUtils, '#field-Date8wbe3d-container');
expectElementToBeVisible(testingUtils, 'Text5asd0a');
expectElementToBeHidden(testingUtils, 'Date8wbe3d');
typeIntoInput(testingUtils, '#Text5asd0a', 'Date');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Date8wbe3d-container');
expectElementToBeVisible(testingUtils, 'Date8wbe3d');
});
});
@ -154,12 +155,12 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeHidden(testingUtils, 'Displayvalue0g6092');
typeIntoInput(testingUtils, '#Text0bq3ar', 'DisplayValue');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeVisible(testingUtils, 'Displayvalue0g6092');
expectInputElementValueIs(testingUtils, '#Displayvalue0g6092', 'No field selected');
});
@ -167,82 +168,82 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(formDisplayValueForm.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-DisplayValueOne-container');
expectElementToBeHidden(testingUtils, 'DisplayValueOne');
typeIntoInput(testingUtils, '#Text0howrc', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-DisplayValueOne-container');
expectElementToBeVisible(testingUtils, 'DisplayValueOne');
expectInputElementValueIs(testingUtils, '#DisplayValueOne', 'No field selected');
typeIntoInput(testingUtils, '#Text0howrc', 'aaab');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-DisplayValueOne-container');
expectElementToBeHidden(testingUtils, 'DisplayValueOne');
});
it('[C309864] - Should be able to see Display value widget when visibility condition refers to another field and form variable', async () => {
formRendererComponent.formDefinition = formService.parseForm(formDisplayValueForm.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-DisplayValueVariableField-container');
expectElementToBeHidden(testingUtils, 'DisplayValueVariableField');
typeIntoInput(testingUtils, '#TextOne', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-DisplayValueVariableField-container');
expectElementToBeVisible(testingUtils, 'DisplayValueVariableField');
expectInputElementValueIs(testingUtils, '#DisplayValueVariableField', 'No field selected');
typeIntoInput(testingUtils, '#TextOne', 'aaab');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-DisplayValueVariableField-container');
expectElementToBeHidden(testingUtils, 'DisplayValueVariableField');
});
it('[C309865] - Should be able to see Display value widget when has multiple visibility conditions and next condition operators', async () => {
formRendererComponent.formDefinition = formService.parseForm(formDisplayValueCombinedVisibility.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Text0bq3ar');
expectElementToBeVisible(testingUtils, '#TextTwo');
expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeVisible(testingUtils, 'Text0bq3ar');
expectElementToBeVisible(testingUtils, 'TextTwo');
expectElementToBeHidden(testingUtils, 'Displayvalue0g6092');
typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa');
expectInputElementValueIs(testingUtils, '#TextTwo', '');
expectElementToBeVisible(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeVisible(testingUtils, 'Displayvalue0g6092');
typeIntoInput(testingUtils, '#Text0bq3ar', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
typeIntoInput(testingUtils, '#Text0bq3ar', 'bbb');
expectInputElementValueIs(testingUtils, '#TextTwo', '');
expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeHidden(testingUtils, 'Displayvalue0g6092');
typeIntoInput(testingUtils, '#TextTwo', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
typeIntoInput(testingUtils, '#Text0bq3ar', 'bbb');
expectInputElementValueIs(testingUtils, '#TextTwo', 'aaa');
expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeHidden(testingUtils, 'Displayvalue0g6092');
typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa');
expectInputElementValueIs(testingUtils, '#TextTwo', 'aaa');
expectElementToBeHidden(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeHidden(testingUtils, 'Displayvalue0g6092');
typeIntoInput(testingUtils, '#TextTwo', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
typeIntoInput(testingUtils, '#Text0bq3ar', 'aaa');
expectInputElementValueIs(testingUtils, '#TextTwo', 'bbb');
expectElementToBeVisible(testingUtils, '#field-Displayvalue0g6092-container');
expectElementToBeVisible(testingUtils, 'Displayvalue0g6092');
});
});
@ -252,24 +253,24 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Number1');
expectElementToBeHidden(testingUtils, '#field-Number2-container');
expectElementToBeVisible(testingUtils, 'Number1');
expectElementToBeHidden(testingUtils, 'Number2');
expect(formRendererComponent.formDefinition.isValid).toBe(true, 'Form should be valid by default');
typeIntoInput(testingUtils, '#Number1', '5');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Number1');
expectElementToBeVisible(testingUtils, '#field-Number2-container');
expectElementToBeVisible(testingUtils, 'Number1');
expectElementToBeVisible(testingUtils, 'Number2');
expect(formRendererComponent.formDefinition.isValid).toBe(true, 'Form should be valid with a valid value');
typeIntoInput(testingUtils, '#Number1', 'az');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Number1');
expectElementToBeHidden(testingUtils, '#field-Number2-container');
expectElementToBeVisible(testingUtils, 'Number1');
expectElementToBeHidden(testingUtils, 'Number2');
expect(formRendererComponent.formDefinition.isValid).toBe(false, 'Form should be invalid with an invalid value');
});
@ -278,18 +279,18 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Text');
expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container');
expectElementToBeVisible(testingUtils, 'Text');
expectElementToBeHidden(testingUtils, 'NumberFieldValue');
typeIntoInput(testingUtils, '#Text', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-NumberFieldValue-container');
expectElementToBeVisible(testingUtils, 'NumberFieldValue');
typeIntoInput(testingUtils, '#Text', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container');
expectElementToBeHidden(testingUtils, 'NumberFieldValue');
});
it('[C315170] - Should be able to complete a task with a form with required number widgets', async () => {
@ -297,18 +298,18 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Number2-container');
expectElementToBeVisible(testingUtils, '#Number1');
expectElementToBeHidden(testingUtils, 'Number2');
expectElementToBeVisible(testingUtils, 'Number1');
typeIntoInput(testingUtils, '#Number1', '5');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Number2-container');
expectElementToBeVisible(testingUtils, 'Number2');
typeIntoInput(testingUtils, '#Number1', '123');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Number2-container');
expectElementToBeHidden(testingUtils, 'Number2');
const errorWidgetText = testingUtils.getByCSS('#field-Number1-container error-widget .adf-error-text').nativeElement;
expect(errorWidgetText.textContent).toBe(`FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`);
expect(formRendererComponent.formDefinition.isValid).toBe(false, 'Form should not be valid without mandatory field');
@ -318,8 +319,8 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(formNumberTextJson.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#NumberReq');
expectElementToBeVisible(testingUtils, '#NumberNotReq');
expectElementToBeVisible(testingUtils, 'NumberReq');
expectElementToBeVisible(testingUtils, 'NumberNotReq');
expect(formRendererComponent.formDefinition.isValid).toBe(false, 'Form should be invalid with an empty required value');
typeIntoInput(testingUtils, '#NumberNotReq', '5');
@ -338,7 +339,13 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(colspanForm.formRepresentation.formDefinition, null, false, true);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div.adf-grid-list');
const numbersContainerGridElement = testingUtils.getByCSS(
`#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div.adf-grid-list`
).nativeElement;
expect(numbersContainerGridElement).toBeTruthy();
expect(numbersContainerGridElement.style.display).not.toBe('hidden');
const sectionGridElement: HTMLElement[] = testingUtils
.getAllByCSS('#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div .adf-grid-list-item')
.map((element) => element.nativeElement);
@ -356,7 +363,13 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(colspanForm.formRepresentation.formDefinition, null, false, false);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container section.adf-grid-list-column-view');
const numbersContainerElement = testingUtils.getByCSS(
`#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container section.adf-grid-list-column-view`
).nativeElement;
expect(numbersContainerElement).toBeTruthy();
expect(numbersContainerElement.style.display).not.toBe('hidden');
const sectionGridElement = testingUtils
.getAllByCSS('#field-2bc275fb-e113-4d-7d-885f-6e74a7332d40-container section .adf-grid-list-single-column')
.map((element) => element.nativeElement);
@ -376,7 +389,10 @@ describe('Form Renderer Component', () => {
const twoSpanTextWidgetContainerId = '#field-1ff21afc-7df4-4607-8363-1dc8576e1c8e-container';
const oneSpanTextWidgetContainerId = '#field-f4285ad-g123-1a73-521d-7nm4a7231aul0-container';
expectElementToBeVisible(testingUtils, `${oneSpanTextWidgetContainerId} section.adf-grid-list-column-view`);
const oneSpanTextWidgetElement = testingUtils.getByCSS(`${oneSpanTextWidgetContainerId} section.adf-grid-list-column-view`).nativeElement;
expect(oneSpanTextWidgetElement).toBeTruthy();
expect(oneSpanTextWidgetElement.style.display).not.toBe('hidden');
const sectionGridElement = testingUtils
.getAllByCSS(`${oneSpanTextWidgetContainerId} section .adf-grid-list-single-column`)
.map((element) => element.nativeElement);
@ -394,7 +410,7 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Number0x8cbv');
expectElementToBeVisible(testingUtils, 'Number0x8cbv');
expectElementToBeValid(testingUtils, 'Number0x8cbv');
testingUtils.blurByCSS('#Number0x8cbv');
@ -434,7 +450,7 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(numberMinMaxForm.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Number0him2z');
expectElementToBeVisible(testingUtils, 'Number0him2z');
expectElementToBeValid(testingUtils, 'Number0him2z');
testingUtils.blurByCSS('#Number0him2z');
@ -475,17 +491,17 @@ describe('Form Renderer Component', () => {
await fixture.whenStable();
const inputText = testingUtils.getByCSS('#Text').nativeElement;
expect(inputText).not.toBeNull();
expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container');
expectElementToBeHidden(testingUtils, 'NumberFieldValue');
typeIntoInput(testingUtils, '#Text', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-NumberFieldValue-container');
expectElementToBeVisible(testingUtils, 'NumberFieldValue');
typeIntoInput(testingUtils, '#Text', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-NumberFieldValue-container');
expectElementToBeHidden(testingUtils, 'NumberFieldValue');
});
it('[C309665] - Should be able to see Number widget when visibility condition refers to another field and form variable', async () => {
@ -494,39 +510,39 @@ describe('Form Renderer Component', () => {
await fixture.whenStable();
const inputText = testingUtils.getByCSS('#Text').nativeElement;
expect(inputText).not.toBeNull();
expectElementToBeHidden(testingUtils, '#field-NumberFieldVariable-container');
expectElementToBeHidden(testingUtils, 'NumberFieldVariable');
typeIntoInput(testingUtils, '#Text', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-NumberFieldVariable-container');
expectElementToBeVisible(testingUtils, 'NumberFieldVariable');
typeIntoInput(testingUtils, '#Text', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-NumberFieldVariable-container');
expectElementToBeHidden(testingUtils, 'NumberFieldVariable');
});
it('[C309666] - Should be able to see Number widget when has multiple visibility conditions and next condition operators', async () => {
formRendererComponent.formDefinition = formService.parseForm(numberWidgetVisibilityForm.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container');
expectElementToBeHidden(testingUtils, 'Number0wxaur');
typeIntoInput(testingUtils, '#Text0hs0gt', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Number0wxaur-container');
expectElementToBeVisible(testingUtils, 'Number0wxaur');
typeIntoInput(testingUtils, '#Text0hs0gt', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container');
expectElementToBeHidden(testingUtils, 'Number0wxaur');
typeIntoInput(testingUtils, '#Text0cuqet', 'aaa');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container');
expectElementToBeHidden(testingUtils, 'Number0wxaur');
expectInputElementValueIs(testingUtils, '#Text0hs0gt', 'bbb');
typeIntoInput(testingUtils, '#Text0hs0gt', 'aaa');
@ -534,7 +550,7 @@ describe('Form Renderer Component', () => {
await fixture.whenStable();
expectInputElementValueIs(testingUtils, '#Text0hs0gt', 'aaa');
expectInputElementValueIs(testingUtils, '#Text0cuqet', 'aaa');
expectElementToBeHidden(testingUtils, '#field-Number0wxaur-container');
expectElementToBeHidden(testingUtils, 'Number0wxaur');
});
});
@ -543,26 +559,26 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(textWidgetVisibility.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#textOne');
expectElementToBeHidden(testingUtils, '#field-textThree-container');
expectElementToBeHidden(testingUtils, '#field-textTwo-container');
expectElementToBeVisible(testingUtils, '#field-textFour-container');
expectElementToBeVisible(testingUtils, 'textOne');
expectElementToBeHidden(testingUtils, 'textThree');
expectElementToBeHidden(testingUtils, 'textTwo');
expectElementToBeVisible(testingUtils, 'textFour');
typeIntoInput(testingUtils, '#textOne', 'Test');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-textOne-container');
expectElementToBeVisible(testingUtils, '#field-textTwo-container');
expectElementToBeVisible(testingUtils, '#field-textThree-container');
expectElementToBeHidden(testingUtils, '#field-textFour-container');
expectElementToBeVisible(testingUtils, 'textOne');
expectElementToBeVisible(testingUtils, 'textTwo');
expectElementToBeVisible(testingUtils, 'textThree');
expectElementToBeHidden(testingUtils, 'textFour');
typeIntoInput(testingUtils, '#textTwo', 'Test');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-textOne-container');
expectElementToBeVisible(testingUtils, '#field-textTwo-container');
expectElementToBeVisible(testingUtils, '#field-textFour-container');
expectElementToBeHidden(testingUtils, '#field-textThree-container');
expectElementToBeVisible(testingUtils, 'textOne');
expectElementToBeVisible(testingUtils, 'textTwo');
expectElementToBeVisible(testingUtils, 'textFour');
expectElementToBeHidden(testingUtils, 'textThree');
});
});
@ -571,13 +587,13 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(radioWidgetVisibilityForm.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text0cee7g-container');
expectElementToBeHidden(testingUtils, '#field-Radiobuttons03rkbo-container');
expectElementToBeVisible(testingUtils, 'Text0cee7g');
expectElementToBeHidden(testingUtils, 'Radiobuttons03rkbo');
typeIntoInput(testingUtils, '#Text0cee7g', 'Radio');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Radiobuttons03rkbo-container');
expectElementToBeVisible(testingUtils, 'Radiobuttons03rkbo');
});
});
@ -587,8 +603,8 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(customWidgetForm.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#Text0vdi18');
expectElementToBeVisible(testingUtils, '#bananaforevah0k8gui');
expectElementToBeVisible(testingUtils, 'Text0vdi18');
expectElementToBeVisible(testingUtils, 'bananaforevah0k8gui');
});
it('Should be able to correctly use visibility in a custom process cloud widget ', async () => {
@ -596,11 +612,11 @@ describe('Form Renderer Component', () => {
formRendererComponent.formDefinition = formService.parseForm(customWidgetFormWithVisibility.formRepresentation.formDefinition);
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-bananaforevah0k8gui-container');
expectElementToBeHidden(testingUtils, 'bananaforevah0k8gui');
typeIntoInput(testingUtils, '#Text0vdi18', 'no');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-bananaforevah0k8gui-container');
expectElementToBeVisible(testingUtils, 'bananaforevah0k8gui');
});
});
@ -641,15 +657,15 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text0id3ic-container');
expectElementToBeVisible(testingUtils, '#field-Number0yggl7-container');
expectElementToBeHidden(testingUtils, '#field-Amount0kceqc-container');
expectElementToBeVisible(testingUtils, 'Text0id3ic');
expectElementToBeVisible(testingUtils, 'Number0yggl7');
expectElementToBeHidden(testingUtils, 'Amount0kceqc');
typeIntoInput(testingUtils, '#Text0id3ic', 'text1');
typeIntoInput(testingUtils, '#Number0yggl7', '77');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Amount0kceqc-container');
expectElementToBeVisible(testingUtils, 'Amount0kceqc');
});
});
@ -659,19 +675,19 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Checkbox0pr51m-container');
expectElementToBeVisible(testingUtils, '#field-Checkbox0fp0zf-container');
expectElementToBeHidden(testingUtils, '#field-Checkbox0lb7ze-container');
expectElementToBeVisible(testingUtils, 'Checkbox0pr51m');
expectElementToBeVisible(testingUtils, 'Checkbox0fp0zf');
expectElementToBeHidden(testingUtils, 'Checkbox0lb7ze');
testingUtils.clickByCSS('#Checkbox0pr51m-input');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Checkbox0lb7ze-container');
expectElementToBeHidden(testingUtils, 'Checkbox0lb7ze');
testingUtils.clickByCSS('#Checkbox0fp0zf-input');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Checkbox0lb7ze-container');
expectElementToBeVisible(testingUtils, 'Checkbox0lb7ze');
});
});
@ -681,18 +697,18 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text-container');
expectElementToBeVisible(testingUtils, '#field-MultilineTextId-container');
expectElementToBeVisible(testingUtils, 'Text');
expectElementToBeVisible(testingUtils, 'MultilineTextId');
typeIntoInput(testingUtils, '#Text', 'textwrong');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-MultilineTextId-container');
expectElementToBeHidden(testingUtils, 'MultilineTextId');
typeIntoInput(testingUtils, '#Text', 'text');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-MultilineTextId-container');
expectElementToBeVisible(testingUtils, 'MultilineTextId');
});
});
@ -702,18 +718,18 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text0tzu53-container');
expectElementToBeHidden(testingUtils, '#field-Displaytext0q4w02-container');
expectElementToBeVisible(testingUtils, 'Text0tzu53');
expectElementToBeHidden(testingUtils, 'Displaytext0q4w02');
typeIntoInput(testingUtils, '#Text0tzu53', 'aaa-value');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Displaytext0q4w02-container');
expectElementToBeVisible(testingUtils, 'Displaytext0q4w02');
typeIntoInput(testingUtils, '#Text0tzu53', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Displaytext0q4w02-container');
expectElementToBeHidden(testingUtils, 'Displaytext0q4w02');
});
it('[C309870] - Should be able to see Display text widget when visibility condition refers to another field and form variable', async () => {
@ -721,18 +737,18 @@ describe('Form Renderer Component', () => {
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Text0tzu53-container');
expectElementToBeHidden(testingUtils, '#field-Displaytext8bac2e-container');
expectElementToBeVisible(testingUtils, 'Text0tzu53');
expectElementToBeHidden(testingUtils, 'Displaytext8bac2e');
typeIntoInput(testingUtils, '#Text0tzu53', 'aaa-variable');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeVisible(testingUtils, '#field-Displaytext8bac2e-container');
expectElementToBeVisible(testingUtils, 'Displaytext8bac2e');
typeIntoInput(testingUtils, '#Text0tzu53', 'bbb');
fixture.detectChanges();
await fixture.whenStable();
expectElementToBeHidden(testingUtils, '#field-Displaytext8bac2e-container');
expectElementToBeHidden(testingUtils, 'Displaytext8bac2e');
});
});
@ -746,4 +762,33 @@ describe('Form Renderer Component', () => {
expect(decimalInputElement.value).toBeTruthy('10.12');
});
});
describe('Section', () => {
it('should be able to set visibility conditions for Section with fields', () => {
const formDefinition = new FormModel(mockSectionVisibilityForm);
const sectionIdSelector = '48e6bfed-b477-4641-86cb-bfa70229a31e';
const mockTextDisplaySectionId = 'Text0n9o62';
const mockTestDisplaySectionFieldId = 'Text0nu6os';
const mockSectionFieldId = 'Text0t23xu';
fixture.componentRef.setInput('formDefinition', formDefinition);
fixture.detectChanges();
expectElementToBeVisible(testingUtils, mockTestDisplaySectionFieldId);
expectElementToBeVisible(testingUtils, mockTextDisplaySectionId);
expectElementToBeHidden(testingUtils, sectionIdSelector);
expectElementToBeHidden(testingUtils, mockSectionFieldId);
typeIntoInput(testingUtils, `#${mockTextDisplaySectionId}`, 'section');
fixture.detectChanges();
expectElementToBeVisible(testingUtils, sectionIdSelector, 'flex');
typeIntoInput(testingUtils, `#${mockTestDisplaySectionFieldId}`, 'section text');
fixture.detectChanges();
expectElementToBeVisible(testingUtils, mockSectionFieldId);
});
});
});

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { JsonPipe, NgClass, NgForOf, NgIf, NgStyle, NgTemplateOutlet, UpperCasePipe } from '@angular/common';
import { JsonPipe, NgClass, NgForOf, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common';
import { Component, Inject, Injector, Input, OnDestroy, OnInit, Optional, ViewEncapsulation } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
@ -28,8 +28,8 @@ import { FormService } from '../services/form.service';
import { FormFieldComponent } from './form-field/form-field.component';
import { FORM_FIELD_MODEL_RENDER_MIDDLEWARE, FormFieldModelRenderMiddleware } from './middlewares/middleware';
import { ContainerModel, FormFieldModel, FormModel, TabModel } from './widgets';
import { FieldStylePipe } from '../pipes/field-style.pipe';
import { HeaderWidgetComponent } from './widgets/header/header.widget';
import { FormSectionComponent } from './form-section/form-section.component';
@Component({
selector: 'adf-form-renderer',
@ -56,10 +56,9 @@ import { HeaderWidgetComponent } from './widgets/header/header.widget';
MatSlideToggleModule,
FormsModule,
JsonPipe,
UpperCasePipe,
FieldStylePipe,
NgClass,
HeaderWidgetComponent
HeaderWidgetComponent,
FormSectionComponent
],
encapsulation: ViewEncapsulation.None
})
@ -154,9 +153,10 @@ export class FormRendererComponent<T> implements OnInit, OnDestroy {
* @param container container model
* @returns the column width for the given model
*/
getColumnWith(container: ContainerModel): string {
const colspan = container ? container.field.colspan : 1;
return (100 / container.field.numberOfColumns) * colspan + '';
getColumnWidth(container: ContainerModel): string {
const { field } = container;
const colspan = field ? field.colspan : 1;
return (100 / field.numberOfColumns) * colspan + '';
}
private runMiddlewareServices(): void {

View File

@ -0,0 +1,11 @@
<div class="adf-grid-list-section-single-column"
[id]="'field-' + field?.id + '-container'"
[style.display]="field?.isVisible ? 'flex' : 'none'">
<div *ngFor="let sectionColumn of field.columns"
[style.width.%]="getSectionColumnWidth(field.numberOfColumns, sectionColumn.fields)"
>
<div *ngFor="let sectionField of sectionColumn.fields" class="adf-grid-list-section-column-view-item">
<adf-form-field [field]="sectionField"/>
</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
.adf-grid-list {
&-section {
&-single-column {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
}
&-column-view-item {
width: 100%;
display: flex;
flex-grow: 1;
box-sizing: border-box;
padding-left: 1%;
padding-right: 1%;
}
}
}

View File

@ -0,0 +1,62 @@
/*!
* @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 { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule, UnitTestingUtils } from '../../../testing';
import { FormFieldModel, FormModel } from '../widgets';
import { FormSectionComponent } from './form-section.component';
import { mockSectionWithFields } from '../mock/form-renderer.component.mock';
describe('FormSectionComponent', () => {
let fixture: ComponentFixture<FormSectionComponent>;
let component: FormSectionComponent;
let testingUtils: UnitTestingUtils;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule]
});
fixture = TestBed.createComponent(FormSectionComponent);
testingUtils = new UnitTestingUtils(fixture.debugElement);
component = fixture.componentInstance;
});
it('should calculate the correct width for section columns', () => {
const numberOfColumns = 3;
const columnField = { colspan: 2 } as FormFieldModel;
const width = component.getSectionColumnWidth(numberOfColumns, [columnField]);
expect(width).toBe('66.66666666666667');
});
it('should handle columns with no colspan defined', () => {
const numberOfColumns = 3;
const columnField = {} as FormFieldModel;
const width = component.getSectionColumnWidth(numberOfColumns, [columnField]);
expect(width).toBe('33.333333333333336');
});
it('should display fields inside section', () => {
const sectionField = new FormFieldModel(new FormModel(), mockSectionWithFields);
fixture.componentRef.setInput('field', sectionField);
fixture.detectChanges();
const sectionFields = testingUtils.getAllByCSS('.adf-grid-list-section-column-view-item adf-form-field');
expect(sectionFields.length).toBe(2);
});
});

View File

@ -0,0 +1,49 @@
/*!
* @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 { Component, inject, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { WidgetVisibilityService } from '../../services/widget-visibility.service';
import { FormFieldModel } from '../widgets/core/form-field.model';
import { FormFieldComponent } from '../form-field/form-field.component';
import { NgFor } from '@angular/common';
@Component({
selector: 'adf-form-section',
standalone: true,
templateUrl: './form-section.component.html',
encapsulation: ViewEncapsulation.None,
styleUrls: ['./form-section.component.scss'],
imports: [NgFor, FormFieldComponent]
})
export class FormSectionComponent implements OnInit {
@Input()
field: FormFieldModel = null;
private readonly visibilityService = inject(WidgetVisibilityService);
ngOnInit(): void {
this.visibilityService.refreshVisibility(this.field.form);
}
getSectionColumnWidth(numberOfColumns: number, columnFields: FormFieldModel[]): string {
const firstColumnFieldIndex = 0;
const defaultColspan = 1;
const fieldColspan = columnFields[firstColumnFieldIndex]?.colspan ?? defaultColspan;
return (100 / numberOfColumns) * fieldColspan + '';
}
}

View File

@ -2135,3 +2135,213 @@ export const displayBigDecimalWidgetMock = {
}
}
};
export const mockSectionWithFields = {
id: '51058d64-b36c-48f0-97a2-d9bd0398edab',
name: 'Section',
type: 'section',
numberOfColumns: 2,
fields: {
1: [
{
id: 'Text07kgd8',
name: 'Text',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
2: [
{
id: 'Date0opbi8',
name: 'Date',
type: 'date',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minValue: null,
maxValue: null,
minDateRangeValue: null,
maxDateRangeValue: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
},
dateDisplayFormat: 'yyyy-MM-dd'
}
]
},
colspan: 1
};
export const mockFormWithSimpleSection = {
id: 'form-363114eb-35f6-40d0-9908-8bbbe776c3e6',
name: 'simplest section',
key: 'simplest-section-uzvc7',
description: '',
version: 0,
formDefinition: {
tabs: [],
fields: [
{
id: 'Group0wuwv7',
name: 'Group',
type: 'group',
tab: null,
params: {
hideHeader: false,
allowCollapse: false,
collapseByDefault: false
},
numberOfColumns: 1,
fields: {
1: [mockSectionWithFields]
}
}
],
outcomes: [],
metadata: {},
variables: []
}
};
export const mockSectionVisibilityForm = {
id: 'form-65e9f07c-44d9-4469-8f5d-74aba3bd7326',
name: 'section visibility',
key: 'section-visibility-tark1',
description: '',
version: 0,
formDefinition: {
tabs: [],
fields: [
{
id: 'Group0sfmn9',
name: 'Group',
type: 'group',
tab: null,
params: {
hideHeader: false,
allowCollapse: false,
collapseByDefault: false
},
numberOfColumns: 1,
fields: {
1: [
{
id: '48e6bfed-b477-4641-86cb-bfa70229a31e',
name: 'Section',
type: 'section',
numberOfColumns: 1,
fields: {
1: [
{
id: 'Text0t23xu',
name: 'Text',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: {
leftType: 'field',
leftValue: 'Text0nu6os',
operator: '==',
rightValue: 'section text',
rightType: 'value',
nextConditionOperator: '',
nextCondition: null
},
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
},
colspan: 1,
visibilityCondition: {
leftType: 'field',
leftValue: 'Text0n9o62',
operator: '==',
rightValue: 'section',
rightType: 'value',
nextConditionOperator: '',
nextCondition: null
}
}
]
}
},
{
id: 'f89d427e-dc4d-44df-b47d-4f61a1cea21f',
name: 'Label',
type: 'container',
tab: null,
numberOfColumns: 2,
fields: {
1: [
{
id: 'Text0n9o62',
name: 'display section',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
2: [
{
id: 'Text0nu6os',
name: 'display section field',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
}
}
],
outcomes: [],
metadata: {},
variables: []
}
};

View File

@ -1406,3 +1406,283 @@ export const fakeViewerForm = {
variables: []
}
};
export const mockFormWithSections = {
id: 'form-970ddfcf-6d5d-4cbb-ae81-958ade65062c',
name: 'simple section',
key: 'simple-section-z54vk',
description: '',
version: 0,
tabs: [],
fields: [
{
id: 'Group0bw66i',
name: 'Group',
type: 'group',
tab: null,
params: {
hideHeader: false,
allowCollapse: false,
collapseByDefault: false
},
numberOfColumns: 1,
fields: {
1: [
{
id: '4c0590a9-44e3-415f-9e69-3a3ffe99066b',
name: 'Section',
type: 'section',
numberOfColumns: 4,
fields: {
1: [
{
id: 'Text0xiuw4',
name: 'Text in section',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: {
leftType: 'field',
leftValue: 'Text0bbed8',
operator: '==',
rightValue: 'text inside',
rightType: 'value',
nextConditionOperator: '',
nextCondition: null
},
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
2: [],
3: [
{
id: 'dateInsideSection',
name: 'Date',
type: 'date',
readOnly: false,
required: true,
colspan: 2,
rowspan: 1,
placeholder: null,
minValue: null,
maxValue: null,
minDateRangeValue: null,
maxDateRangeValue: null,
visibilityCondition: {
leftType: 'field',
leftValue: 'Text0s7k3m',
operator: '==',
rightValue: 'date',
rightType: 'value',
nextConditionOperator: '',
nextCondition: null
},
params: {
existingColspan: 1,
maxColspan: 2
},
dateDisplayFormat: 'yyyy-MM-dd'
}
]
},
colspan: 1
},
{
id: '9d0ed068-f6b9-4ddc-ba8b-8ac29b745e1d',
name: 'Section',
type: 'section',
numberOfColumns: 6,
fields: {
1: [
{
id: 'Text0dkzym',
name: 'Text',
type: 'text',
readOnly: false,
required: false,
colspan: 2,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
2: [
{
id: 'Text0kn8qb',
name: 'type text outside',
type: 'text',
readOnly: false,
required: false,
colspan: 2,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
3: [
{
id: 'Text0bbed8',
name: 'type text inside',
type: 'text',
readOnly: false,
required: false,
colspan: 2,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
},
colspan: 1
}
]
}
},
{
id: 'fc73e4fa-5725-41b2-9185-624d9efd6581',
name: 'Label',
type: 'container',
tab: null,
numberOfColumns: 4,
fields: {
1: [
{
id: 'Text0p0v95',
name: 'type section',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
},
{
id: 'Text0s7k3m',
name: 'type date',
type: 'text',
readOnly: false,
required: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
2: [
{
id: 'multilineOutsideSection',
name: 'Multiline text',
type: 'multi-line-text',
readOnly: false,
colspan: 1,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
required: false,
visibilityCondition: null,
params: {
existingColspan: 1,
maxColspan: 2
}
}
],
3: [
{
id: 'Text0opb9w',
name: 'Text',
type: 'text',
readOnly: false,
required: false,
colspan: 2,
rowspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
regexPattern: null,
visibilityCondition: {
leftType: 'field',
leftValue: 'Text0kn8qb',
operator: '==',
rightValue: 'text outside',
rightType: 'value',
nextConditionOperator: '',
nextCondition: null
},
params: {
existingColspan: 1,
maxColspan: 2
}
}
]
}
}
],
outcomes: [],
metadata: {},
variables: [],
rules: {
fields: {
Text0bbed8: {
click: [
{
actions: [
{
target: 'field.Text0xiuw4',
payload: {
value: 'updated by form rules!'
}
}
]
}
]
}
}
}
};

View File

@ -15,16 +15,15 @@
* limitations under the License.
*/
/* eslint-disable @angular-eslint/component-selector */
import { FormFieldModel } from './form-field.model';
export class ContainerColumnModel {
size: number = 12;
fields: any[] = [];
fields: FormFieldModel[] = [];
colspan: number = 1;
rowspan: number = 1;
hasFields(): boolean {
return this.fields && this.fields.length > 0;
return !!this.fields?.length;
}
}

View File

@ -879,6 +879,21 @@ describe('FormFieldModel', () => {
expect(field.numberOfColumns).toBe(123);
});
it('should NOT update colspan based on fields colspans for section type', () => {
const form = new FormModel();
const json = {
type: FormFieldTypes.SECTION,
numberOfColumns: 2,
fields: {
column1: [{ id: 'field1', colspan: 2 }],
column2: [{ id: 'field2', colspan: 3 }]
}
};
const formField = new FormFieldModel(form, json);
expect(formField.colspan).toBe(1);
});
it('should instantiate FormField when has no variable', () => {
const form = new FormModel({});
form.json = {

View File

@ -282,30 +282,34 @@ export class FormFieldModel extends FormWidgetModel {
}
private containerFactory(json: any, form: any): void {
this.numberOfColumns = json.numberOfColumns || 1;
this.fields = json.fields;
const { numberOfColumns = 1, fields = {} } = json;
this.numberOfColumns = numberOfColumns;
this.fields = fields;
this.rowspan = 1;
this.colspan = 1;
if (json.fields) {
for (const currentField in json.fields) {
if (Object.prototype.hasOwnProperty.call(json.fields, currentField)) {
const col = new ContainerColumnModel();
col.fields = (json.fields[currentField] || []).map((field) => new FormFieldModel(form, field));
col.rowspan = json.fields[currentField].length;
col.fields.forEach((colFields: any) => {
this.colspan = colFields.colspan > this.colspan ? colFields.colspan : this.colspan;
});
this.rowspan = this.rowspan < col.rowspan ? col.rowspan : this.rowspan;
this.columns.push(col);
}
Object.keys(fields).forEach((currentField) => {
if (!Object.prototype.hasOwnProperty.call(fields, currentField)) {
return;
}
}
const col = new ContainerColumnModel();
col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field));
col.rowspan = fields[currentField].length;
if (!FormFieldTypes.isSectionType(this.type)) {
this.updateContainerColspan(col.fields);
}
this.rowspan = Math.max(this.rowspan, col.rowspan);
this.columns.push(col);
});
}
private updateContainerColspan(fields: FormFieldModel[]): void {
fields.forEach((colField: FormFieldModel) => {
this.colspan = Math.max(this.colspan, colField.colspan);
});
}
parseValue(json: any): any {

View File

@ -24,7 +24,7 @@ import { FormFieldModel } from './form-field.model';
import { FormOutcomeModel } from './form-outcome.model';
import { FormModel } from './form.model';
import { TabModel } from './tab.model';
import { fakeMetadataForm, mockDisplayExternalPropertyForm } from '../../mock/form.mock';
import { fakeMetadataForm, mockDisplayExternalPropertyForm, mockFormWithSections } from '../../mock/form.mock';
import { CoreTestingModule } from '../../../../testing';
import { TestBed } from '@angular/core/testing';
@ -643,4 +643,68 @@ describe('FormModel', () => {
expect(FormFieldTypes.isConstantValueType(displayExternalPropertyWidget.type)).toBeTrue();
expect(displayExternalPropertyWidget.value).toBe('hr');
});
describe('getFormFields', () => {
let form: FormModel;
beforeEach(() => {
form = new FormModel(mockFormWithSections);
});
it('should get all form fields (containers, sections, fields)', () => {
const fields = form.getFormFields();
expect(fields.length).toBe(13);
});
it('should filter form fields by type inside sections', () => {
const fields = form.getFormFields([FormFieldTypes.DATE]);
expect(fields.length).toBe(1);
expect(fields[0].id).toBe('dateInsideSection');
expect(fields[0].type).toBe(FormFieldTypes.DATE);
});
it('should filter form fields by type outside sections', () => {
const fields = form.getFormFields([FormFieldTypes.MULTILINE_TEXT]);
expect(fields.length).toBe(1);
expect(fields[0].id).toBe('multilineOutsideSection');
expect(fields[0].type).toBe(FormFieldTypes.MULTILINE_TEXT);
});
it('should filter form fields by multiple types', () => {
const fields = form.getFormFields([FormFieldTypes.DATE, FormFieldTypes.MULTILINE_TEXT]);
expect(fields.length).toBe(2);
expect(fields[0].id).toBe('dateInsideSection');
expect(fields[1].id).toBe('multilineOutsideSection');
expect(fields[0].type).toBe(FormFieldTypes.DATE);
expect(fields[1].type).toBe(FormFieldTypes.MULTILINE_TEXT);
});
it('should return no fields when filtered by a non-existent type', () => {
const fields = form.getFormFields(['NON_EXISTENT_TYPE']);
expect(fields.length).toBe(0);
});
it('should return fields from cache if available', () => {
form.fieldsCache = [new FormFieldModel(form, { type: FormFieldTypes.TEXT }), new FormFieldModel(form, { type: FormFieldTypes.NUMBER })];
const fields = form.getFormFields();
expect(fields.length).toBe(2);
expect(fields[0].type).toBe(FormFieldTypes.TEXT);
expect(fields[1].type).toBe(FormFieldTypes.NUMBER);
});
it('should return filtered fields from cache if available', () => {
form.fieldsCache = [
new FormFieldModel(form, { type: FormFieldTypes.TEXT }),
new FormFieldModel(form, { type: FormFieldTypes.AMOUNT }),
new FormFieldModel(form, { type: FormFieldTypes.DATE }),
new FormFieldModel(form, { type: FormFieldTypes.NUMBER })
];
const fields = form.getFormFields([FormFieldTypes.AMOUNT, FormFieldTypes.DATE, FormFieldTypes.NUMBER]);
expect(fields.length).toBe(3);
expect(fields[0].type).toBe(FormFieldTypes.AMOUNT);
expect(fields[1].type).toBe(FormFieldTypes.DATE);
expect(fields[2].type).toBe(FormFieldTypes.NUMBER);
});
});
});

View File

@ -340,28 +340,63 @@ export class FormModel implements ProcessFormModel {
return this.fieldsCache.find((field) => field.id === fieldId);
}
getFormFields(): FormFieldModel[] {
if (this.fieldsCache.length > 0) {
return this.fieldsCache;
} else {
const formFieldModel: FormFieldModel[] = [];
for (let i = 0; i < this.fields.length; i++) {
const field = this.fields[i];
if (field instanceof ContainerModel) {
formFieldModel.push(field.field);
field.field.columns.forEach((column) => {
formFieldModel.push(...column.fields);
});
} else {
formFieldModel.push(field);
}
}
return formFieldModel;
getFormFields(filterTypes?: string[]): FormFieldModel[] {
if (this.fieldsCache?.length) {
return this.filterFieldsByType(this.fieldsCache, filterTypes);
}
const formFieldModel: FormFieldModel[] = [];
this.processFields(this.fields, formFieldModel);
return this.filterFieldsByType(formFieldModel, filterTypes);
}
private processFields(fields: (ContainerModel | FormFieldModel)[], formFieldModel: FormFieldModel[]): void {
fields.forEach((field) => {
if (this.isSectionField(field)) {
this.handleSectionField(field, formFieldModel);
} else if (this.isContainerField(field)) {
this.handleContainerField(field, formFieldModel);
} else {
this.handleSingleField(field, formFieldModel);
}
});
}
private isContainerField(field: ContainerModel | FormFieldModel): field is ContainerModel {
return field instanceof ContainerModel;
}
private isSectionField(field: ContainerModel | FormFieldModel): field is FormFieldModel {
return field.type === FormFieldTypes.SECTION;
}
private handleSectionField(section: FormFieldModel, formFieldModel: FormFieldModel[]): void {
formFieldModel.push(section);
section.columns.forEach((column) => {
this.processFields(column.fields, formFieldModel);
});
}
private handleContainerField(container: ContainerModel, formFieldModel: FormFieldModel[]): void {
formFieldModel.push(container.field);
container.field.columns.forEach((column) => {
this.processFields(column.fields, formFieldModel);
});
}
private handleSingleField(field: FormFieldModel, formFieldModel: FormFieldModel[]): void {
formFieldModel.push(field);
if (field.fields) {
this.processFields(Object.values(field.fields), formFieldModel);
}
}
private filterFieldsByType(fields: FormFieldModel[], types?: string[]): FormFieldModel[] {
if (!types?.length) {
return fields;
}
return fields.filter((field) => types.includes(field?.type));
}
markAsInvalid(): void {

View File

@ -4,4 +4,5 @@
font-weight: var(--adf-readonly-text-font-weight, var(--adf-form-label-font-weight, var(--theme-font-weight)));
color: var(--adf-readonly-text-color, var(--adf-form-label-color, var(--theme-text-color)));
line-height: normal;
word-break: break-word;
}

View File

@ -152,12 +152,12 @@ describe('FormComponent UI and visibility', () => {
fixture.detectChanges();
const firstEl = fixture.debugElement.query(By.css('#field-country-container'));
expect(firstEl.nativeElement.style.visibility).toBe('hidden');
expect(firstEl.nativeElement.style.display).toBe('none');
const secondEl = fixture.debugElement.query(By.css('#name'));
expect(secondEl).not.toBeNull();
expect(secondEl).toBeDefined();
expect(fixture.nativeElement.querySelector('#field-name-container').style.visibility).not.toBe('hidden');
expect(fixture.nativeElement.querySelector('#field-name-container').style.display).toBe('block');
});
it('should hide the field based on the previous one', () => {
@ -171,10 +171,10 @@ describe('FormComponent UI and visibility', () => {
const firstEl = fixture.debugElement.query(By.css('#name'));
expect(firstEl).not.toBeNull();
expect(firstEl).toBeDefined();
expect(fixture.nativeElement.querySelector('#field-name-container').style.visibility).not.toBe('hidden');
expect(fixture.nativeElement.querySelector('#field-name-container').style.display).toBe('block');
const secondEl = fixture.debugElement.query(By.css('#field-country-container'));
expect(secondEl.nativeElement.style.visibility).toBe('hidden');
expect(secondEl.nativeElement.style.display).toBe('none');
});
it('should show the hidden field when the visibility condition change to true', () => {
@ -186,10 +186,10 @@ describe('FormComponent UI and visibility', () => {
fixture.detectChanges();
let firstEl = fixture.debugElement.query(By.css('#field-country-container'));
expect(firstEl.nativeElement.style.visibility).toBe('hidden');
expect(firstEl.nativeElement.style.display).toBe('none');
const secondEl = fixture.debugElement.query(By.css('#field-name-container'));
expect(secondEl.nativeElement.style.visibility).not.toBe('hidden');
expect(secondEl.nativeElement.style.display).toBe('block');
const inputElement = fixture.nativeElement.querySelector('#name');
inputElement.value = 'italy';
@ -197,7 +197,7 @@ describe('FormComponent UI and visibility', () => {
fixture.detectChanges();
firstEl = fixture.debugElement.query(By.css('#field-country-container'));
expect(firstEl.nativeElement.style.visibility).not.toBe('hidden');
expect(firstEl.nativeElement.style.display).toBe('block');
});
});

View File

@ -511,9 +511,9 @@ describe('TaskFormComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
const inputThreeContainer = fixture.nativeElement.querySelector('#field-text3-container');
expect(inputThreeContainer.style.visibility).toBe('hidden');
expect(inputThreeContainer.style.display).toBe('none');
const completeOutcomeButton: HTMLButtonElement = fixture.nativeElement.querySelector('#adf-form-complete');
expect(completeOutcomeButton.style.visibility).not.toBe('hidden');
expect(completeOutcomeButton.style.display).not.toBe('none');
completeOutcomeButton.click();
fixture.detectChanges();
});