mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-02 17:53:30 +00:00
AAE-39559 Update to handle 0 initial rows for repeatable sections (#12113)
* AAE-39559 Update to handle 0 initial rows for repeatable sections * address code review comments
This commit is contained in:
@@ -50,6 +50,7 @@ Contains the value and metadata for a field of a [`Form`](../../../lib/process-s
|
||||
| numberOfColumns | number | 1 | Number of columns defined by a container field |
|
||||
| fields | [`FormFieldModel`](../../core/models/form-field.model.md)\[] | \[] | Fields contained within a container field |
|
||||
| columns | [`ContainerColumnModel`](../../../lib/core/src/lib/form/components/widgets/core/container-column.model.ts)\[] | \[] | Column definitions for a container field |
|
||||
| rows | [`ContainerRowModel`](../../../lib/core/src/lib/form/components/widgets/core/container-row.model.ts)\[] | \[] | Row definitions for a repeatable section field |
|
||||
| emptyOption | [`FormFieldOption`](../../../lib/core/src/lib/form/components/widgets/core/form-field-option.ts) | | Dropdown menu item to use when no option is chosen |
|
||||
| validationSummary | string | | Error/information message added during field validation (see [`FormFieldValidator`](../../../lib/core/src/lib/form/components/widgets/core/form-field-validator.ts) interface) |
|
||||
|
||||
@@ -95,6 +96,17 @@ The [REST Call Task 101](https://community.alfresco.com/community/bpm/blog/2016/
|
||||
tutorial on the [APS community site](https://community.alfresco.com/community/bpm)
|
||||
contains full details about how the REST calls work, along with a worked example.
|
||||
|
||||
### Repeatable sections
|
||||
|
||||
Repeatable section fields (`type: 'repeatable-section'`) support an `initialNumberOfRows` parameter in `params`.
|
||||
Setting `initialNumberOfRows` to `0` is valid: the section starts with no runtime rows and users add rows via the **Add row** action.
|
||||
|
||||
When no rows exist, `columns` holds a design-time column template (used by form editors and form-rules).
|
||||
Template fields are bound to the form for rendering but are marked as templates and do not write to `form.values`.
|
||||
No row data is written to `form.values` until the user adds a row or saved values are loaded from the server.
|
||||
|
||||
If saved values are present on the field (`value` array), row count is derived from `value.length` (subject to `maxNumberOfRows`), regardless of `initialNumberOfRows`.
|
||||
|
||||
## See also
|
||||
|
||||
- [Extensibility](../../user-guide/extensibility.md)
|
||||
|
||||
@@ -1542,6 +1542,103 @@ describe('FormFieldModel', () => {
|
||||
field = new FormFieldModel(form, json);
|
||||
});
|
||||
|
||||
describe('zero initial rows', () => {
|
||||
const zeroInitialRowsJson = {
|
||||
...json,
|
||||
params: {
|
||||
...json.params,
|
||||
initialNumberOfRows: 0
|
||||
}
|
||||
};
|
||||
|
||||
it('should create column template without rows when initialNumberOfRows is 0', () => {
|
||||
const freshForm = new FormModel();
|
||||
const zeroInitialRowsField = new FormFieldModel(freshForm, zeroInitialRowsJson);
|
||||
|
||||
expect(zeroInitialRowsField.rows.length).toBe(0);
|
||||
expect(zeroInitialRowsField.columns.length).toBe(2);
|
||||
|
||||
const templateFieldIds = zeroInitialRowsField.columns.flatMap((column) => column.fields.map((templateField) => templateField.id));
|
||||
expect(templateFieldIds.some((id) => id.startsWith('Text0wwp7n-Row'))).toBe(true);
|
||||
expect(templateFieldIds.some((id) => id.startsWith('Integer0rzkwq-Row'))).toBe(true);
|
||||
zeroInitialRowsField.columns
|
||||
.flatMap((column) => column.fields)
|
||||
.forEach((templateField) => {
|
||||
expect(templateField.form).toBe(freshForm);
|
||||
expect(templateField.parent?.isTemplate).toBe(true);
|
||||
});
|
||||
|
||||
expect(freshForm.values[json.id]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not create rows after reload when initialNumberOfRows is 0 and no values were saved', () => {
|
||||
const freshForm = new FormModel();
|
||||
new FormFieldModel(freshForm, zeroInitialRowsJson);
|
||||
|
||||
expect(freshForm.values[json.id]).toBeUndefined();
|
||||
|
||||
const reloadedField = new FormFieldModel(freshForm, {
|
||||
...zeroInitialRowsJson,
|
||||
value: freshForm.values[json.id]
|
||||
});
|
||||
|
||||
expect(reloadedField.rows.length).toBe(0);
|
||||
expect(freshForm.values[json.id]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should create rows from saved values when initialNumberOfRows is 0', () => {
|
||||
const savedValuesJson = {
|
||||
...zeroInitialRowsJson,
|
||||
value: [
|
||||
{
|
||||
Text0wwp7n: 'saved text',
|
||||
Dropdown0e7tn4: null,
|
||||
Integer0rzkwq: 42,
|
||||
Dropdown0wgm63: null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const freshForm = new FormModel();
|
||||
const fieldWithSavedValues = new FormFieldModel(freshForm, savedValuesJson);
|
||||
|
||||
expect(fieldWithSavedValues.rows.length).toBe(1);
|
||||
expect(fieldWithSavedValues.rows[0].columns[0].fields[0].value).toBe('saved text');
|
||||
});
|
||||
|
||||
it('should reuse column template on subsequent updateForm calls when initialNumberOfRows is 0', () => {
|
||||
const freshForm = new FormModel();
|
||||
const zeroInitialRowsField = new FormFieldModel(freshForm, zeroInitialRowsJson);
|
||||
const templateColumns = zeroInitialRowsField.columns;
|
||||
|
||||
zeroInitialRowsField.updateForm();
|
||||
|
||||
expect(zeroInitialRowsField.columns).toBe(templateColumns);
|
||||
});
|
||||
|
||||
it('should apply read-only state to column template when initialNumberOfRows is 0', () => {
|
||||
const freshForm = new FormModel();
|
||||
const zeroInitialRowsField = new FormFieldModel(freshForm, zeroInitialRowsJson);
|
||||
|
||||
zeroInitialRowsField.readOnly = true;
|
||||
|
||||
zeroInitialRowsField.columns
|
||||
.flatMap((column) => column.fields)
|
||||
.forEach((templateField) => {
|
||||
expect(templateField.readOnly).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add first row when initialNumberOfRows is 0', () => {
|
||||
const zeroInitialRowsField = new FormFieldModel(form, zeroInitialRowsJson);
|
||||
|
||||
zeroInitialRowsField.addRow(zeroInitialRowsField.fields, form);
|
||||
|
||||
expect(zeroInitialRowsField.rows.length).toBe(1);
|
||||
expect(zeroInitialRowsField.rows[0].columns[0].fields[0].id).toContain('Text0wwp7n-Row');
|
||||
});
|
||||
});
|
||||
|
||||
describe('add row', () => {
|
||||
const assignFormRulesEventSubject = (): Subject<FormRulesEvent> => {
|
||||
const formRulesEvent = new Subject<FormRulesEvent>();
|
||||
|
||||
@@ -30,7 +30,7 @@ import { DataColumn } from '../../../../datatable/data/data-column.model';
|
||||
import { DateFnsUtils } from '../../../../common';
|
||||
import { isValid as isValidDate } from 'date-fns';
|
||||
import { ContainerRowModel } from './container-row.model';
|
||||
import { RepeatableSectionModel, ROW_ID_PREFIX } from './repeatable-section.model';
|
||||
import { RepeatableSectionModel, ROW_ID_PREFIX, TEMPLATE_ROW_ID } from './repeatable-section.model';
|
||||
import { formFieldRuleHandler } from './handlers/form-field-rule.handler';
|
||||
import { formFieldVisibilityConditionHandler } from './handlers/form-field-visibility-condition.handler';
|
||||
|
||||
@@ -351,7 +351,12 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
this.rows.push(this.createRow(fields, form, i, value?.[i], i < params?.initialNumberOfRows));
|
||||
}
|
||||
|
||||
this.columns = this.rows[0].columns;
|
||||
if (this.rows.length > 0) {
|
||||
this.columns = this.rows[0].columns;
|
||||
} else if (this.columns.length === 0) {
|
||||
// Design-time column template for Studio and form-rules; isTemplate suppresses form.values writes.
|
||||
this.columns = this.createColumns(fields, form, TEMPLATE_ROW_ID, 0, undefined, true);
|
||||
}
|
||||
}
|
||||
|
||||
private getNumberOfRows(initialNrRows: number = 1, maxNrRows: number | null = null, value?: any) {
|
||||
@@ -366,7 +371,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
return row;
|
||||
}
|
||||
|
||||
private createColumns(fields: any, form: any, rowId: string, index?: number, value?: any) {
|
||||
private createColumns(fields: any, form: any, rowId: string, index?: number, value?: any, isTemplate: boolean = false) {
|
||||
const columns: ContainerColumnModel[] = [];
|
||||
|
||||
Object.keys(fields).forEach((currentField) => {
|
||||
@@ -382,7 +387,8 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
uid: this.getUniqueId(field, rowId),
|
||||
fields: this.fields,
|
||||
rowIndex: index ?? 0,
|
||||
value: field.type === FormFieldTypes.SECTION ? value : value?.[field.id]
|
||||
value: field.type === FormFieldTypes.SECTION ? value : value?.[field.id],
|
||||
isTemplate
|
||||
})
|
||||
);
|
||||
col.rowspan = fields[currentField].length;
|
||||
@@ -399,15 +405,24 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
}
|
||||
|
||||
private updateRepeatableSectionReadOnlyState(state: boolean) {
|
||||
for (const row of this.rows) {
|
||||
for (const column of row.columns) {
|
||||
for (const field of column.fields) {
|
||||
if (field.type === FormFieldTypes.SECTION) {
|
||||
this.updateInnerSectionReadOnlyState(field, state);
|
||||
}
|
||||
if (this.rows.length > 0) {
|
||||
for (const row of this.rows) {
|
||||
this.updateRepeatableSectionColumnsReadOnlyState(row.columns, state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state);
|
||||
this.updateRepeatableSectionColumnsReadOnlyState(this.columns, state);
|
||||
}
|
||||
|
||||
private updateRepeatableSectionColumnsReadOnlyState(columns: ContainerColumnModel[], state: boolean) {
|
||||
for (const column of columns) {
|
||||
for (const field of column.fields) {
|
||||
if (field.type === FormFieldTypes.SECTION) {
|
||||
this.updateInnerSectionReadOnlyState(field, state);
|
||||
}
|
||||
|
||||
field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,7 +609,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
}
|
||||
|
||||
updateForm() {
|
||||
if (!this.form) {
|
||||
if (!this.form || this.parent?.isTemplate) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,15 @@
|
||||
|
||||
export const ROW_ID_PREFIX = '-Row';
|
||||
|
||||
/** Placeholder row id for design-time column templates when no rows exist (not used for runtime rows). */
|
||||
export const TEMPLATE_ROW_ID = '0';
|
||||
|
||||
export interface RepeatableSectionModel {
|
||||
id: string;
|
||||
uid: string;
|
||||
fields: any;
|
||||
rowIndex: number;
|
||||
value?: any;
|
||||
/** When true, field is a design-time column template and must not write to form.values. */
|
||||
isTemplate?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user