mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-44184 Colspan setting on form fields is ignored or not properly applied (#11800)
* AAE-44184 Colspan setting on form fields is ignored or not properly applied * AAE-44184 Better handling of empty columns * AAE-44184 Fixing stale adf-form-field instances * AAE-44184 Consolidating duplicate code
This commit is contained in:
@@ -1,94 +1,116 @@
|
||||
<div id="adf-form-renderer" class="{{ formDefinition.className }} adf-form-renderer"
|
||||
[ngClass]="{ 'adf-readonly-form': formDefinition.readOnly }">
|
||||
<div *ngIf="formDefinition.hasTabs()">
|
||||
<div *ngIf="hasTabs()" class="alfresco-tabs-widget">
|
||||
@if (formDefinition.hasTabs()) {
|
||||
@if (hasTabs()) {
|
||||
<div class="alfresco-tabs-widget">
|
||||
<mat-tab-group [preserveContent]="true">
|
||||
<mat-tab *ngFor="let tab of visibleTabs()" [label]="tab.title | translate ">
|
||||
@for (tab of visibleTabs(); track tab) {
|
||||
<mat-tab [label]="tab.title | translate ">
|
||||
<ng-template matTabContent>
|
||||
<div class="adf-form-tab-content">
|
||||
<ng-template *ngTemplateOutlet="render; context: { fieldToRender: tab.fields }" />
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
}
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div *ngIf="!formDefinition.hasTabs() && formDefinition.hasFields()">
|
||||
@if (!formDefinition.hasTabs() && formDefinition.hasFields()) {
|
||||
<div>
|
||||
<ng-template *ngTemplateOutlet="render; context: { fieldToRender: formDefinition.fields }" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<ng-template #render let-fieldToRender="fieldToRender">
|
||||
<div *ngFor="let currentRootElement of fieldToRender">
|
||||
<div *ngIf="currentRootElement.type === 'section'" [id]="'field-' + currentRootElement?.id + '-container'" class="adf-container-widget">
|
||||
<adf-form-section [field]="currentRootElement" />
|
||||
@for (currentRootElement of fieldToRender; track currentRootElement) {
|
||||
@if (currentRootElement.type === 'section') {
|
||||
<div [id]="'field-' + currentRootElement?.id + '-container'" class="adf-container-widget">
|
||||
<adf-form-section [field]="currentRootElement.field" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div *ngIf="currentRootElement.type === 'container' || currentRootElement.type === 'group'"
|
||||
@if (currentRootElement.type === 'container' || currentRootElement.type === 'group') {
|
||||
<div
|
||||
[id]="'field-' + currentRootElement?.id + '-container'"
|
||||
class="adf-container-widget"
|
||||
[hidden]="!currentRootElement?.isVisible">
|
||||
<adf-header-widget [element]="currentRootElement" />
|
||||
<div *ngIf="currentRootElement?.form?.enableFixedSpace; else fixingTemplate">
|
||||
<div class="adf-grid-list"
|
||||
[ngStyle]="{ 'grid-template-columns': 'repeat(' + getNumberOfColumns(currentRootElement) + ', 1fr)' }"
|
||||
*ngIf="currentRootElement?.isExpanded">
|
||||
<div class="adf-grid-list-item"
|
||||
*ngFor="let field of getContainerFields(currentRootElement)"
|
||||
[ngStyle]="{ 'grid-area': 'auto / auto / span ' + (field?.rowspan || 1) + ' / span ' + (field?.colspan || 1) }">
|
||||
<adf-form-field *ngIf="field" [field]="field" />
|
||||
@if (currentRootElement?.form?.enableFixedSpace) {
|
||||
@if (currentRootElement?.isExpanded) {
|
||||
<div class="adf-grid-list"
|
||||
[ngStyle]="{ 'grid-template-columns': 'repeat(' + getNumberOfColumns(currentRootElement) + ', 1fr)' }">
|
||||
@for (field of getContainerFields(currentRootElement); track field ?? $index) {
|
||||
<div class="adf-grid-list-item"
|
||||
[ngStyle]="{ 'grid-area': 'auto / auto / span ' + (field?.rowspan || 1) + ' / span ' + (field?.colspan || 1) }">
|
||||
@if (field) {
|
||||
<adf-form-field [field]="field" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<ng-template [ngTemplateOutlet]="fixingTemplate" />
|
||||
}
|
||||
|
||||
<ng-template #fixingTemplate>
|
||||
<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.%]="getColumnWidth(currentRootElement)"
|
||||
>
|
||||
<ng-container *ngFor="let field of column?.fields">
|
||||
<ng-container *ngIf="field.type === 'section'; else formField">
|
||||
<adf-form-section [field]="field"/>
|
||||
</ng-container>
|
||||
<ng-template #formField>
|
||||
<div class="adf-grid-list-column-view-item">
|
||||
<adf-form-field [field]="field"/>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
@if (currentRootElement?.isExpanded) {
|
||||
<section class="adf-grid-list-column-view">
|
||||
@for (column of currentRootElement?.columns; track column; let columnIndex = $index) {
|
||||
<div
|
||||
class="adf-grid-list-single-column"
|
||||
[style.width.%]="getColumnWidth(currentRootElement, currentRootElement?.columns, columnIndex)">
|
||||
@for (field of column?.fields; track field) {
|
||||
@if (field.type === 'section') {
|
||||
<adf-form-section [field]="field"/>
|
||||
} @else {
|
||||
<div class="adf-grid-list-column-view-item">
|
||||
<adf-form-field [field]="field"/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<ng-template #columnViewItem let-column="column">
|
||||
<div class="adf-grid-list-column-view-item" *ngFor="let field of column?.fields">
|
||||
<adf-form-field *ngIf="field" [field]="field" />
|
||||
</div>
|
||||
@for (field of column?.fields; track field) {
|
||||
<div class="adf-grid-list-column-view-item">
|
||||
@if (field) {
|
||||
<adf-form-field [field]="field" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentRootElement.type === 'repeatable-section') {
|
||||
<div
|
||||
<div
|
||||
[id]="'field-' + currentRootElement?.id + '-container'"
|
||||
class="adf-container-widget"
|
||||
[hidden]="!currentRootElement?.isVisible"
|
||||
>
|
||||
<adf-repeat-widget [element]="currentRootElement" [isEditor]="false">
|
||||
@for (row of currentRootElement.field.rows; track row.id; let rowIndex = $index) {
|
||||
@for (row of currentRootElement.field.rows; track row; let rowIndex = $index) {
|
||||
@let hasMultipleRows = currentRootElement.field.rows.length > 1;
|
||||
<div
|
||||
<div
|
||||
class="adf-grid-list-container"
|
||||
[class.adf-grid-list-container-multiple]="hasMultipleRows"
|
||||
[class.adf-grid-list-container-disabled]="currentRootElement.field.readOnly"
|
||||
>
|
||||
>
|
||||
<div class="adf-grid-list-row">
|
||||
<span class="adf-grid-list-row-label">{{ 'FORM.FORM_RENDERER.ROW_LABEL' | translate: {number: rowIndex + 1} }}</span>
|
||||
|
||||
@let shouldDisplayRemoveRowButton = !currentRootElement.field.rows[rowIndex].isInitial || (currentRootElement.field.rows[rowIndex].isInitial && currentRootElement.field.params.allowInitialRowsDelete);
|
||||
@if (shouldDisplayRemoveRowButton) {
|
||||
<button
|
||||
<button
|
||||
mat-icon-button
|
||||
class="adf-grid-list-row-remove-button"
|
||||
[disabled]="currentRootElement.field.readOnly"
|
||||
@@ -100,21 +122,20 @@
|
||||
}
|
||||
</div>
|
||||
<section class="adf-grid-list-column-view">
|
||||
@for (column of row.columns; track $index) {
|
||||
<div
|
||||
class="adf-grid-list-single-column"
|
||||
[style.width.%]="getColumnWidth(currentRootElement)"
|
||||
>
|
||||
@for (field of column?.fields; track $index) {
|
||||
@if (field.type === 'section') {
|
||||
<adf-form-section [field]="field"/>
|
||||
} @else {
|
||||
<div class="adf-grid-list-column-view-item">
|
||||
<adf-form-field [field]="field"/>
|
||||
</div>
|
||||
@for (column of row.columns; track column; let columnIndex = $index) {
|
||||
<div
|
||||
class="adf-grid-list-single-column"
|
||||
[style.width.%]="getColumnWidth(currentRootElement, row.columns, columnIndex)">
|
||||
@for (field of column?.fields; track field) {
|
||||
@if (field.type === 'section') {
|
||||
<adf-form-section [field]="field"/>
|
||||
} @else {
|
||||
<div class="adf-grid-list-column-view-item">
|
||||
<adf-form-field [field]="field"/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
@@ -123,13 +144,16 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<div *ngIf="currentRootElement.type === 'dynamic-table'" class="adf-container-widget">
|
||||
<adf-form-field [field]="currentRootElement" />
|
||||
</div>
|
||||
@if (currentRootElement.type === 'dynamic-table') {
|
||||
<div class="adf-container-widget">
|
||||
<adf-form-field [field]="currentRootElement" />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="adf-container-widget"
|
||||
*ngIf="currentRootElement.type === 'readonly' && currentRootElement.field.params.field.type === 'dynamic-table'">
|
||||
<adf-form-field [field]="currentRootElement.field"/>
|
||||
</div>
|
||||
</div>
|
||||
@if (currentRootElement.type === 'readonly' && currentRootElement.field.params.field.type === 'dynamic-table') {
|
||||
<div class="adf-container-widget">
|
||||
<adf-form-field [field]="currentRootElement.field"/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { FormRendererComponent } from './form-renderer.component';
|
||||
import {
|
||||
amountWidgetFormVisibilityMock,
|
||||
checkboxWidgetFormVisibilityMock,
|
||||
colspanAnyColumnsForm,
|
||||
colspanForm,
|
||||
customWidgetForm,
|
||||
customWidgetFormWithVisibility,
|
||||
@@ -439,6 +440,65 @@ describe('Form Renderer Component', () => {
|
||||
expect(fullWidthElement.style['width']).toBe('100%');
|
||||
});
|
||||
|
||||
it('Should display widths 25% and 75% in non-grid mode for 4-column container with colspan 1 and 3', async () => {
|
||||
formRendererComponent.formDefinition = formService.parseForm(colspanAnyColumnsForm.formRepresentation.formDefinition, null, false, false);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const columns = testingUtils
|
||||
.getAllByCSS('#field-4col-container-id-container section.adf-grid-list-column-view .adf-grid-list-single-column')
|
||||
.map((element) => element.nativeElement as HTMLElement);
|
||||
|
||||
expect(columns.length).toBe(4);
|
||||
expect(columns[0].style['width']).toBe('25%');
|
||||
expect(columns[1].style['width']).toBe('75%');
|
||||
expect(columns[2].style['width']).toBe('0%');
|
||||
expect(columns[3].style['width']).toBe('0%');
|
||||
});
|
||||
|
||||
it('Should display widths 33.333% and 66.666% in non-grid mode for 12-column container with colspan 4 and 8', async () => {
|
||||
formRendererComponent.formDefinition = formService.parseForm(colspanAnyColumnsForm.formRepresentation.formDefinition, null, false, false);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const columns = testingUtils
|
||||
.getAllByCSS('#field-12col-container-id-container section.adf-grid-list-column-view .adf-grid-list-single-column')
|
||||
.map((element) => element.nativeElement as HTMLElement);
|
||||
|
||||
expect(columns.length).toBe(12);
|
||||
expect(parseFloat(columns[0].style['width'])).toBeCloseTo(33.33, 2);
|
||||
expect(columns[1].style['width']).toBe('0%');
|
||||
expect(columns[2].style['width']).toBe('0%');
|
||||
expect(columns[3].style['width']).toBe('0%');
|
||||
expect(parseFloat(columns[4].style['width'])).toBeCloseTo(66.67, 2);
|
||||
expect(columns[5].style['width']).toBe('0%');
|
||||
expect(columns[6].style['width']).toBe('0%');
|
||||
expect(columns[7].style['width']).toBe('0%');
|
||||
expect(columns[8].style['width']).toBe('0%');
|
||||
expect(columns[9].style['width']).toBe('0%');
|
||||
expect(columns[10].style['width']).toBe('0%');
|
||||
expect(columns[11].style['width']).toBe('0%');
|
||||
});
|
||||
|
||||
it('Should preserve width for an intentionally empty leading spacer column in non-grid mode', async () => {
|
||||
formRendererComponent.formDefinition = formService.parseForm(
|
||||
amountWidgetFormVisibilityMock.formRepresentation.formDefinition,
|
||||
null,
|
||||
false,
|
||||
false
|
||||
);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const columns = testingUtils
|
||||
.getAllByCSS('#field-ce32844c-20b2-4361-88b5-a56f28219aef-container section.adf-grid-list-column-view .adf-grid-list-single-column')
|
||||
.map((element) => element.nativeElement as HTMLElement);
|
||||
|
||||
expect(columns.length).toBe(2);
|
||||
expect(columns[0].style['width']).toBe('50%');
|
||||
expect(columns[1].style['width']).toBe('50%');
|
||||
});
|
||||
|
||||
it('[C309872] - Should display Text widget spans on 2 columns when colspan is set to 2', () => {
|
||||
formRendererComponent.formDefinition = formService.parseForm(colspanForm.formRepresentation.formDefinition, null, false, false);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NgClass, NgForOf, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common';
|
||||
import { NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component, DestroyRef, inject, Injector, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { filter } from 'rxjs';
|
||||
@@ -36,11 +36,12 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { ConfirmDialogComponent } from '../../../lib/dialogs/confirm-dialog/confirm.dialog';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { IconModule } from '../../icon/icon.module';
|
||||
import { FormLayoutColumn, getFormLayoutColumnWidth } from './helpers/column-width';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-form-renderer',
|
||||
templateUrl: './form-renderer.component.html',
|
||||
styleUrls: ['./form-renderer.component.scss'],
|
||||
styleUrl: './form-renderer.component.scss',
|
||||
providers: [
|
||||
{
|
||||
provide: FormRulesManager,
|
||||
@@ -54,9 +55,7 @@ import { IconModule } from '../../icon/icon.module';
|
||||
}
|
||||
],
|
||||
imports: [
|
||||
NgIf,
|
||||
MatTabsModule,
|
||||
NgForOf,
|
||||
NgTemplateOutlet,
|
||||
TranslatePipe,
|
||||
MatButtonModule,
|
||||
@@ -182,16 +181,8 @@ export class FormRendererComponent<T> implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the column width based on the numberOfColumns and current field's colspan property
|
||||
*
|
||||
* @param container container model
|
||||
* @returns the column width for the given model
|
||||
*/
|
||||
getColumnWidth(container: ContainerModel): string {
|
||||
const { field } = container;
|
||||
const colspan = field ? field.colspan : 1;
|
||||
return (100 / field.numberOfColumns) * colspan + '';
|
||||
getColumnWidth(container: ContainerModel, columns: FormLayoutColumn[], columnIndex: number): string {
|
||||
return getFormLayoutColumnWidth(container.field?.numberOfColumns, columns, columnIndex);
|
||||
}
|
||||
|
||||
private runMiddlewareServices(): void {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<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>
|
||||
@for (sectionColumn of field.columns; track sectionColumn; let columnIndex = $index) {
|
||||
<div [style.width.%]="getSectionColumnWidth(field.numberOfColumns, field.columns, columnIndex)">
|
||||
@for (sectionField of sectionColumn.fields; track sectionField) {
|
||||
<div class="adf-grid-list-section-column-view-item">
|
||||
<adf-form-field [field]="sectionField"/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -38,17 +38,17 @@ describe('FormSectionComponent', () => {
|
||||
|
||||
it('should calculate the correct width for section columns', () => {
|
||||
const numberOfColumns = 3;
|
||||
const columnField = { colspan: 2 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 2 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, [columnField]);
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 0);
|
||||
expect(width).toBe('66.66666666666667');
|
||||
});
|
||||
|
||||
it('should handle columns with no colspan defined', () => {
|
||||
const numberOfColumns = 3;
|
||||
const columnField = {} as FormFieldModel;
|
||||
const columns = [{ fields: [{} as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, [columnField]);
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 0);
|
||||
expect(width).toBe('33.333333333333336');
|
||||
});
|
||||
|
||||
@@ -63,84 +63,93 @@ describe('FormSectionComponent', () => {
|
||||
|
||||
describe('getSectionColumnWidth', () => {
|
||||
it('should cap width at 100% when numberOfColumns is not a number', () => {
|
||||
const columnField = { colspan: 2 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 2 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth('invalid' as unknown as number, [columnField]);
|
||||
const width = component.getSectionColumnWidth('invalid' as unknown as number, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should cap width at 100% when numberOfColumns is null', () => {
|
||||
const columnField = { colspan: 3 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 3 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(null as unknown as number, [columnField]);
|
||||
const width = component.getSectionColumnWidth(null as unknown as number, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should return 100% when numberOfColumns is undefined', () => {
|
||||
const columnField = { colspan: 1 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 1 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(undefined as unknown as number, [columnField]);
|
||||
const width = component.getSectionColumnWidth(undefined as unknown as number, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should cap width at 100% when numberOfColumns is 0', () => {
|
||||
const columnField = { colspan: 2 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 2 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(0, [columnField]);
|
||||
const width = component.getSectionColumnWidth(0, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should cap width at 100% when numberOfColumns is negative', () => {
|
||||
const columnField = { colspan: 3 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 3 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(-1, [columnField]);
|
||||
const width = component.getSectionColumnWidth(-1, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should return 100 when numberOfColumns is falsy and no colspan is defined', () => {
|
||||
const columnField = {} as FormFieldModel;
|
||||
const columns = [{ fields: [{} as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(null as unknown as number, [columnField]);
|
||||
const width = component.getSectionColumnWidth(null as unknown as number, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should calculate percentage width when numberOfColumns is a valid number', () => {
|
||||
const numberOfColumns = 4;
|
||||
const columnField = { colspan: 2 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 2 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, [columnField]);
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 0);
|
||||
expect(width).toBe('50');
|
||||
});
|
||||
|
||||
it('should cap width at 100% when colspan exceeds numberOfColumns', () => {
|
||||
const numberOfColumns = 2;
|
||||
const columnField = { colspan: 5 } as FormFieldModel;
|
||||
const columns = [{ fields: [{ colspan: 5 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, [columnField]);
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should use default colspan of 1 when field has no colspan and numberOfColumns is valid', () => {
|
||||
const numberOfColumns = 5;
|
||||
const columnField = {} as FormFieldModel;
|
||||
const columns = [{ fields: [{} as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, [columnField]);
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 0);
|
||||
expect(width).toBe('20');
|
||||
});
|
||||
|
||||
it('should handle empty columnFields array', () => {
|
||||
it('should return default width for an authored empty spacer column', () => {
|
||||
const numberOfColumns = 3;
|
||||
const columns = [{ fields: [] }, { fields: [{ colspan: 1 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, []);
|
||||
expect(parseFloat(width)).toBeCloseTo(33.33);
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 0);
|
||||
expect(width).toBe('33.333333333333336');
|
||||
});
|
||||
|
||||
it('should use first field colspan when multiple fields are provided', () => {
|
||||
const numberOfColumns = 2;
|
||||
const columnFields = [{ colspan: 1 } as FormFieldModel, { colspan: 3 } as FormFieldModel];
|
||||
it('should return 0 width for an empty column covered by a previous colspan', () => {
|
||||
const numberOfColumns = 4;
|
||||
const columns = [{ fields: [{ colspan: 3 } as FormFieldModel] }, { fields: [] }, { fields: [] }, { fields: [] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columnFields);
|
||||
expect(width).toBe('50');
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 1);
|
||||
expect(width).toBe('0');
|
||||
});
|
||||
|
||||
it('should use max field colspan when multiple fields are provided', () => {
|
||||
const numberOfColumns = 2;
|
||||
const columns = [{ fields: [{ colspan: 1 } as FormFieldModel, { colspan: 3 } as FormFieldModel] }];
|
||||
|
||||
const width = component.getSectionColumnWidth(numberOfColumns, columns, 0);
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,14 +19,14 @@ import { Component, inject, Input, OnInit, ViewEncapsulation } from '@angular/co
|
||||
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';
|
||||
import { FormLayoutColumn, getFormLayoutColumnWidth } from '../helpers/column-width';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-form-section',
|
||||
templateUrl: './form-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styleUrls: ['./form-section.component.scss'],
|
||||
imports: [NgFor, FormFieldComponent]
|
||||
styleUrl: './form-section.component.scss',
|
||||
imports: [FormFieldComponent]
|
||||
})
|
||||
export class FormSectionComponent implements OnInit {
|
||||
@Input()
|
||||
@@ -38,15 +38,7 @@ export class FormSectionComponent implements OnInit {
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
|
||||
getSectionColumnWidth(numberOfColumns: number, columnFields: FormFieldModel[]): string {
|
||||
const firstColumnFieldIndex = 0;
|
||||
const defaultColspan = 1;
|
||||
const fieldColspan = columnFields[firstColumnFieldIndex]?.colspan ?? defaultColspan;
|
||||
|
||||
if (typeof numberOfColumns !== 'number' || !numberOfColumns || numberOfColumns <= 0) {
|
||||
numberOfColumns = 1;
|
||||
}
|
||||
|
||||
return Math.min(100, (100 / numberOfColumns) * fieldColspan) + '';
|
||||
getSectionColumnWidth(numberOfColumns: number, columns: FormLayoutColumn[], columnIndex: number): string {
|
||||
return getFormLayoutColumnWidth(numberOfColumns, columns, columnIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*!
|
||||
* @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 { FormLayoutColumn, getFormLayoutColumnWidth } from './column-width';
|
||||
|
||||
describe('getFormLayoutColumnWidth', () => {
|
||||
it('should return the default width when numberOfColumns is invalid', () => {
|
||||
const columns: FormLayoutColumn[] = [{ fields: [{ colspan: 1 }] }];
|
||||
|
||||
const width = getFormLayoutColumnWidth(undefined, columns, 0);
|
||||
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should return the default width for an authored empty spacer column', () => {
|
||||
const NUMBER_OF_COLUMNS = 3;
|
||||
const columns: FormLayoutColumn[] = [{ fields: [] }, { fields: [{ colspan: 1 }] }];
|
||||
|
||||
const width = getFormLayoutColumnWidth(NUMBER_OF_COLUMNS, columns, 0);
|
||||
|
||||
expect(width).toBe('33.333333333333336');
|
||||
});
|
||||
|
||||
it('should return zero width for an empty column covered by a previous colspan', () => {
|
||||
const NUMBER_OF_COLUMNS = 4;
|
||||
const columns: FormLayoutColumn[] = [{ fields: [{ colspan: 3 }] }, { fields: [] }, { fields: [] }, { fields: [] }];
|
||||
|
||||
const width = getFormLayoutColumnWidth(NUMBER_OF_COLUMNS, columns, 1);
|
||||
|
||||
expect(width).toBe('0');
|
||||
});
|
||||
|
||||
it('should use the maximum colspan when a column contains multiple fields', () => {
|
||||
const NUMBER_OF_COLUMNS = 4;
|
||||
const columns: FormLayoutColumn[] = [{ fields: [{ colspan: 1 }, { colspan: 3 }] }];
|
||||
|
||||
const width = getFormLayoutColumnWidth(NUMBER_OF_COLUMNS, columns, 0);
|
||||
|
||||
expect(width).toBe('75');
|
||||
});
|
||||
|
||||
it('should cap the width at 100 percent when colspan exceeds the available columns', () => {
|
||||
const NUMBER_OF_COLUMNS = 2;
|
||||
const columns: FormLayoutColumn[] = [{ fields: [{ colspan: 5 }] }];
|
||||
|
||||
const width = getFormLayoutColumnWidth(NUMBER_OF_COLUMNS, columns, 0);
|
||||
|
||||
expect(width).toBe('100');
|
||||
});
|
||||
|
||||
it('should treat a missing colspan as one column', () => {
|
||||
const NUMBER_OF_COLUMNS = 5;
|
||||
const columns: FormLayoutColumn[] = [{ fields: [{}] }];
|
||||
|
||||
const width = getFormLayoutColumnWidth(NUMBER_OF_COLUMNS, columns, 0);
|
||||
|
||||
expect(width).toBe('20');
|
||||
});
|
||||
|
||||
it('should not mark a later empty spacer as covered when an earlier column is also empty', () => {
|
||||
const NUMBER_OF_COLUMNS = 4;
|
||||
const columns: FormLayoutColumn[] = [{ fields: [] }, { fields: [] }, { fields: [{ colspan: 1 }] }, { fields: [] }];
|
||||
|
||||
const width = getFormLayoutColumnWidth(NUMBER_OF_COLUMNS, columns, 1);
|
||||
|
||||
expect(width).toBe('25');
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export interface FormLayoutField {
|
||||
colspan?: number | null;
|
||||
}
|
||||
|
||||
export interface FormLayoutColumn {
|
||||
fields?: FormLayoutField[] | null;
|
||||
}
|
||||
|
||||
export const getFormLayoutColumnWidth = (
|
||||
numberOfColumns: number | null | undefined,
|
||||
columns: FormLayoutColumn[] | null | undefined,
|
||||
columnIndex: number
|
||||
): string => {
|
||||
const normalizedColumnCount = typeof numberOfColumns === 'number' && numberOfColumns > 0 ? numberOfColumns : 1;
|
||||
const defaultColumnWidth = 100 / normalizedColumnCount;
|
||||
const columnFields = columns?.[columnIndex]?.fields ?? [];
|
||||
|
||||
if (columnFields.length === 0) {
|
||||
return isColumnCoveredByPreviousField(columns, columnIndex) ? '0' : `${defaultColumnWidth}`;
|
||||
}
|
||||
|
||||
const maxColspan = Math.max(...columnFields.map((field) => field.colspan || 1));
|
||||
return `${Math.min(100, defaultColumnWidth * maxColspan)}`;
|
||||
};
|
||||
|
||||
const isColumnCoveredByPreviousField = (columns: FormLayoutColumn[] | null | undefined, columnIndex: number): boolean => {
|
||||
if (!columns || columnIndex <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let previousColumnIndex = 0; previousColumnIndex < columnIndex; previousColumnIndex++) {
|
||||
const previousFields = columns[previousColumnIndex]?.fields ?? [];
|
||||
|
||||
if (previousFields.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previousColumnSpan = Math.max(...previousFields.map((field) => field.colspan || 1));
|
||||
if (previousColumnIndex + previousColumnSpan > columnIndex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -981,6 +981,130 @@ export const colspanForm = {
|
||||
}
|
||||
};
|
||||
|
||||
export const colspanAnyColumnsForm = {
|
||||
formRepresentation: {
|
||||
id: 'form-any-columns-colspan',
|
||||
name: 'any-columns-colspan',
|
||||
description: '',
|
||||
version: 0,
|
||||
standAlone: true,
|
||||
formDefinition: {
|
||||
tabs: [],
|
||||
fields: [
|
||||
{
|
||||
id: '4col-container-id',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
tab: null,
|
||||
numberOfColumns: 4,
|
||||
fields: {
|
||||
'1': [
|
||||
{
|
||||
id: 'Text4ColLeft',
|
||||
name: 'Left',
|
||||
type: 'text',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
colspan: 1,
|
||||
placeholder: null,
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
regexPattern: null,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 4
|
||||
}
|
||||
}
|
||||
],
|
||||
'2': [
|
||||
{
|
||||
id: 'Text4ColRight',
|
||||
name: 'Right',
|
||||
type: 'text',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
colspan: 3,
|
||||
placeholder: null,
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
regexPattern: null,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 4
|
||||
}
|
||||
}
|
||||
],
|
||||
'3': [],
|
||||
'4': []
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '12col-container-id',
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
tab: null,
|
||||
numberOfColumns: 12,
|
||||
fields: {
|
||||
'1': [
|
||||
{
|
||||
id: 'Text12ColLeft',
|
||||
name: 'Left',
|
||||
type: 'text',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
colspan: 4,
|
||||
placeholder: null,
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
regexPattern: null,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 12
|
||||
}
|
||||
}
|
||||
],
|
||||
'2': [],
|
||||
'3': [],
|
||||
'4': [],
|
||||
'5': [
|
||||
{
|
||||
id: 'Text12ColRight',
|
||||
name: 'Right',
|
||||
type: 'text',
|
||||
readOnly: false,
|
||||
required: false,
|
||||
colspan: 8,
|
||||
placeholder: null,
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
regexPattern: null,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 12
|
||||
}
|
||||
}
|
||||
],
|
||||
'6': [],
|
||||
'7': [],
|
||||
'8': [],
|
||||
'9': [],
|
||||
'10': [],
|
||||
'11': [],
|
||||
'12': []
|
||||
}
|
||||
}
|
||||
],
|
||||
outcomes: [],
|
||||
metadata: {},
|
||||
variables: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const numberNotRequiredForm = {
|
||||
formRepresentation: {
|
||||
id: 'form-d4c462db-3838-442e-a006-171e6ccafe61',
|
||||
|
||||
Reference in New Issue
Block a user