From b3e8ff27af8e3a7c7b652bef582305dc11895ba4 Mon Sep 17 00:00:00 2001 From: Pablo Martinez Garcia Date: Fri, 16 Oct 2020 15:36:58 +0200 Subject: [PATCH] [AAE-3587] Use grid to allow using rowspan (#6151) * AAE-3587 Use material grid to allow using rowspan * AAE-3587 Fix column field rowspan * AAE-3587 Add enableFixedSpace default value * AAE-3587 Change material grid to CSS grid area * AAE-3587 Review comments * AAE-3587 Refactor for clarification of the complex method * AAE-3587 Add types to method signatures --- .../form-renderer.component.spec.ts | 10 +- .../widgets/container/container.widget.html | 10 +- .../widgets/container/container.widget.scss | 10 +- .../container/container.widget.spec.ts | 134 ++++++++++++++---- .../widgets/container/container.widget.ts | 92 +++++++++--- .../widgets/core/form-field.model.ts | 1 + .../components/widgets/core/form.model.ts | 2 +- 7 files changed, 201 insertions(+), 58 deletions(-) diff --git a/lib/core/form/components/form-renderer.component.spec.ts b/lib/core/form/components/form-renderer.component.spec.ts index 8fbde5ba22..7300165775 100644 --- a/lib/core/form/components/form-renderer.component.spec.ts +++ b/lib/core/form/components/form-renderer.component.spec.ts @@ -329,15 +329,15 @@ describe('Form Renderer Component', () => { formRendererComponent.formDefinition = formService.parseForm(colspanForm.formRepresentation.formDefinition, null , false, false); fixture.detectChanges(); await fixture.whenStable(); - const formSizedElement = fixture.nativeElement.querySelector('#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container section.adf-grid-list-column-view'); + const formSizedElement = fixture.nativeElement.querySelector('#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div.adf-grid-list'); expectElementToBeVisible(formSizedElement); - const sectionGridElement: HTMLElement[] = fixture.nativeElement.querySelectorAll('#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container section .adf-grid-list-single-column'); + const sectionGridElement: HTMLElement[] = fixture.nativeElement.querySelectorAll('#field-2bc275fb-e113-4d7d-885f-6e74a7332d40-container div .adf-grid-list-item'); sectionGridElement.forEach((element) => { - expect(element.style['width']).toBe('50%', 'Elemens is wrong sized for this section'); + expect(element.style['grid-area']).toBe('auto / auto / span 1 / span 1', 'Elemens is wrong sized for this section'); }); - const fullWidthElement = fixture.nativeElement.querySelector('#field-d52ada4e-cbdc-4f0c-a480-5b85fa00e4f8-container section.adf-grid-list-column-view .adf-grid-list-single-column'); - expect(fullWidthElement.style['width']).toBe('100%'); + const fullWidthElement = fixture.nativeElement.querySelector('#field-d52ada4e-cbdc-4f0c-a480-5b85fa00e4f8-container div.adf-grid-list .adf-grid-list-item'); + expect(fullWidthElement.style['grid-area']).toBe('auto / auto / span 1 / span 2'); }); it('[C309655] - Should display validation error message when Number widget has invalid value', async () => { diff --git a/lib/core/form/components/widgets/container/container.widget.html b/lib/core/form/components/widgets/container/container.widget.html index ab26552b29..bc7ae6cea5 100644 --- a/lib/core/form/components/widgets/container/container.widget.html +++ b/lib/core/form/components/widgets/container/container.widget.html @@ -12,11 +12,13 @@
-
-
+
+
-
+
@@ -28,5 +30,3 @@ - - diff --git a/lib/core/form/components/widgets/container/container.widget.scss b/lib/core/form/components/widgets/container/container.widget.scss index b360e79854..502a36e7b1 100644 --- a/lib/core/form/components/widgets/container/container.widget.scss +++ b/lib/core/form/components/widgets/container/container.widget.scss @@ -57,14 +57,10 @@ } .adf-grid-list { - display: flex; - flex-wrap: wrap; - margin-left: -1%; - margin-right: -1%; + display: grid; } .adf-grid-list-item { - flex-grow: 1; box-sizing: border-box; padding-left: 1%; padding-right: 1%; @@ -119,5 +115,9 @@ width: 80%; } + adf-form-field { + width: 100%; + } + } } diff --git a/lib/core/form/components/widgets/container/container.widget.spec.ts b/lib/core/form/components/widgets/container/container.widget.spec.ts index 7d902264bd..be90cd7f8c 100644 --- a/lib/core/form/components/widgets/container/container.widget.spec.ts +++ b/lib/core/form/components/widgets/container/container.widget.spec.ts @@ -17,7 +17,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ContainerColumnModel } from './../core/container-column.model'; import { FormFieldTypes } from './../core/form-field-types'; import { FormFieldModel } from './../core/form-field.model'; import { FormModel } from './../core/form.model'; @@ -103,7 +102,7 @@ describe('ContainerWidgetComponent', () => { it('should send an event when a value is changed in the form', (done) => { const fakeForm = new FormModel(); - const fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'}); + const fakeField = new FormFieldModel(fakeForm, { id: 'fakeField', value: 'fakeValue' }); widget.fieldChanged.subscribe((field) => { expect(field).not.toBe(null); expect(field.id).toBe('fakeField'); @@ -117,30 +116,22 @@ describe('ContainerWidgetComponent', () => { describe('fields', () => { it('should serializes the content fields', () => { - const field1 = {id: '1'}, - field2 = {id: '2'}, - field3 = {id: '3'}, - field4 = {id: '4'}, - field5 = {id: '5'}, - field6 = {id: '6'}; + const form = new FormModel(); + const json = { + id: 'test', + name: 'test', + type: 'container', + tab: null, + fields: { + '1' : [{ id: '1' }, { id: '2' }, { id: '3' }], + '2' : [{ id: '4' }, { id: '5' }], + '3' : [{ id: '6' }] + } + }; - const container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel())); - container.columns = [ - { fields: [ - field1, - field2, - field3 - ] }, - { fields: [ - field4, - field5 - ] }, - { fields: [ - field6 - ] } - ]; - - widget.content = container; + const field = new FormFieldModel(form, json); + widget.field = field; + widget.ngOnInit(); expect(widget.fields[0].id).toEqual('1'); expect(widget.fields[1].id).toEqual('4'); @@ -152,6 +143,99 @@ describe('ContainerWidgetComponent', () => { expect(widget.fields[7]).toEqual(undefined); expect(widget.fields[8]).toEqual(undefined); }); + + it('should serializes the content fields with rowspan', () => { + const form = new FormModel(); + const json = { + id: 'test', + name: 'test', + type: 'container', + tab: null, + fields: { + '1': [ + { + id: 'a', + colspan: 2, + rowspan: 1 + }, + { + id: 'b' + }, + { + id: 'c' + }, + { + id: 'd' + }, + { + id: 'e', + colspan: 3 + }, + { + id: 'f' + }, + { + id: 'g' + }, + { + id: 'h', + colspan: 2 + } + ], + '2': [ + { + id: '1', + rowspan: 3 + }, + { + id: '2', + rowspan: 2, + colspan: 2 + }, + { + id: '3' + } + ], + '3': [ + { + id: 'white' + }, + { + id: 'black' + }, + { + id: 'green', + rowspan: 2 + }, + { + id: 'yellow' + } + ] + } + }; + const field = new FormFieldModel(form, json); + widget.field = field; + widget.ngOnInit(); + + expect(widget.fields.length).toEqual(17); + expect(widget.fields[0].id).toEqual('a'); + expect(widget.fields[1].id).toEqual('white'); + expect(widget.fields[2].id).toEqual('b'); + expect(widget.fields[3].id).toEqual('1'); + expect(widget.fields[4].id).toEqual('black'); + expect(widget.fields[5].id).toEqual('c'); + expect(widget.fields[6].id).toEqual('green'); + expect(widget.fields[7].id).toEqual('d'); + expect(widget.fields[8].id).toEqual('e'); + expect(widget.fields[9].id).toEqual('f'); + expect(widget.fields[10].id).toEqual('2'); + expect(widget.fields[11].id).toEqual('g'); + expect(widget.fields[12].id).toEqual('h'); + expect(widget.fields[13].id).toEqual('yellow'); + expect(widget.fields[14]).toEqual(undefined); + expect(widget.fields[15].id).toEqual('3'); + expect(widget.fields[16]).toEqual(undefined); + }); }); describe('getColumnWith', () => { diff --git a/lib/core/form/components/widgets/container/container.widget.ts b/lib/core/form/components/widgets/container/container.widget.ts index f0ac4e0c78..c77523efe1 100644 --- a/lib/core/form/components/widgets/container/container.widget.ts +++ b/lib/core/form/components/widgets/container/container.widget.ts @@ -15,7 +15,7 @@ * limitations under the License. */ - /* tslint:disable:component-selector */ +/* tslint:disable:component-selector */ import { AfterViewInit, Component, OnInit, ViewEncapsulation } from '@angular/core'; import { FormService } from './../../../services/form.service'; @@ -43,9 +43,11 @@ import { ContainerWidgetComponentModel } from './container.widget.model'; export class ContainerWidgetComponent extends WidgetComponent implements OnInit, AfterViewInit { content: ContainerWidgetComponentModel; + numberOfColumns: number; + fields: FormFieldModel[]; constructor(public formService: FormService) { - super(formService); + super(formService); } onExpanderClicked() { @@ -57,34 +59,90 @@ export class ContainerWidgetComponent extends WidgetComponent implements OnInit, ngOnInit() { if (this.field) { this.content = new ContainerWidgetComponentModel(this.field); + this.getNumberOfColumnsFromTheBiggestBetweenJsonAndColumnsLengthOrOne(); + this.fields = this.getFields(); } } + private getNumberOfColumnsFromTheBiggestBetweenJsonAndColumnsLengthOrOne() { + this.numberOfColumns = (this.content.json?.numberOfColumns || 1) > (this.content.columns?.length || 1) ? + (this.content.json?.numberOfColumns || 1) : + (this.content.columns?.length || 1); + } + /** * Serializes column fields */ - get fields(): FormFieldModel[] { - const fields = []; + private getFields(): FormFieldModel[] { + const { size, rowspanOffset, numberOfColumnElementsToBeProcessedRemaining , fields } = this.initializeHelpers(); - let rowContainsElement = true, - rowIndex = 0; - - while (rowContainsElement) { - rowContainsElement = false; - for (let i = 0; i < this.content.columns.length; i++ ) { - const field = this.content.columns[i].fields[rowIndex]; - if (field) { - rowContainsElement = true; + for (let i = 0; i < size; i++) { + let fieldExist = false; + let columnIndex = 0; + while (columnIndex < this.numberOfColumns) { + let field: FormFieldModel; + if (rowspanOffset[columnIndex] > 0) { + this.decreaseRowspanOffsetForColumn(rowspanOffset, columnIndex); + } else { + field = this.getNextFieldToAdd(columnIndex, numberOfColumnElementsToBeProcessedRemaining, field); + fields.push(field); + if (field) { + fieldExist = true; + } + this.updateColumnsRowspanOffsetWithFieldRowspan(field, rowspanOffset, columnIndex); + numberOfColumnElementsToBeProcessedRemaining[columnIndex] = numberOfColumnElementsToBeProcessedRemaining[columnIndex] - 1; } - - fields.push(field); + columnIndex = columnIndex + (field?.colspan || 1); + } + if (!fieldExist) { + i = this.deleteLastEmptyRowAndExit(fields, i, size); } - rowIndex++; } - return fields; } + private updateColumnsRowspanOffsetWithFieldRowspan(field: FormFieldModel, rowspanOffset: any[], columnIndex: number) { + for (let k = 0; k < (field?.colspan || 1); k++) { + rowspanOffset[columnIndex + k] = field?.rowspan > 0 ? field?.rowspan - 1 : 0; + } + } + + private getNextFieldToAdd(columnIndex: number, numberOfColumnElementsToBeProcessedRemaining: any[], field: FormFieldModel): FormFieldModel { + const rowToCompute = (this.content.columns[columnIndex]?.fields?.length || 0) - numberOfColumnElementsToBeProcessedRemaining[columnIndex]; + field = this.content.columns[columnIndex]?.fields[rowToCompute]; + return field; + } + + private decreaseRowspanOffsetForColumn(rowspanOffset: any[], columnIndex: number) { + rowspanOffset[columnIndex] = rowspanOffset[columnIndex] - 1; + } + + private initializeHelpers(): { + size: number; + rowspanOffset: number[]; + numberOfColumnElementsToBeProcessedRemaining: number[]; + fields: FormFieldModel[]; + } { + const fields = []; + const numberOfColumnElementsToBeProcessedRemaining: number[] = []; + const rowspanOffset: number[] = []; + let size = 0; + for (let i = 0; i < this.numberOfColumns; i++) { + numberOfColumnElementsToBeProcessedRemaining.push(this.content.columns[i]?.fields?.length || 0); + rowspanOffset[i] = 0; + size += (this.content.columns[i]?.fields?.length || 0); + } + return { size, rowspanOffset, numberOfColumnElementsToBeProcessedRemaining, fields }; + } + + private deleteLastEmptyRowAndExit(fields: FormFieldModel[], i: number, size: number) { + for (let j = 0; j < this.numberOfColumns; j++) { + fields.pop(); + } + i = size; + return i; + } + /** * Calculate the column width based on the numberOfColumns and current field's colspan property * diff --git a/lib/core/form/components/widgets/core/form-field.model.ts b/lib/core/form/components/widgets/core/form-field.model.ts index dd65b609be..5dd787af12 100644 --- a/lib/core/form/components/widgets/core/form-field.model.ts +++ b/lib/core/form/components/widgets/core/form-field.model.ts @@ -152,6 +152,7 @@ export class FormFieldModel extends FormWidgetModel { this.restIdProperty = json.restIdProperty; this.restLabelProperty = json.restLabelProperty; this.colspan = json.colspan; + this.rowspan = json.rowspan; this.minLength = json.minLength || 0; this.maxLength = json.maxLength || 0; this.minValue = json.minValue; diff --git a/lib/core/form/components/widgets/core/form.model.ts b/lib/core/form/components/widgets/core/form.model.ts index 88721c13ff..163bc1689d 100644 --- a/lib/core/form/components/widgets/core/form.model.ts +++ b/lib/core/form/components/widgets/core/form.model.ts @@ -99,7 +99,7 @@ export class FormModel { this.className = json.className || ''; this.variables = json.variables || []; this.processVariables = json.processVariables || []; - this.enableFixedSpace = enableFixedSpace; + this.enableFixedSpace = enableFixedSpace || true; const tabCache: FormWidgetModelCache = {};