mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-27107 Improve repeatable section widget (#11336)
* AAE-27107 Improve logic * AAE-27107 Add tests * AAE-27107 Fix logic * AAE-27107 Fix test * AAE-27107 Fix updateForm logic
This commit is contained in:
@@ -81,8 +81,24 @@
|
||||
<div
|
||||
class="adf-grid-list-container"
|
||||
[class.adf-grid-list-container-multiple]="hasMultipleRows"
|
||||
>
|
||||
<h4 class="adf-grid-list-container-label">{{ 'FORM.FORM_RENDERER.ROW_LABEL' | translate: {number: rowIndex + 1} }}</h4>
|
||||
[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
|
||||
mat-icon-button
|
||||
class="adf-grid-list-row-remove-button"
|
||||
[disabled]="currentRootElement.field.readOnly"
|
||||
[matTooltip]="'FORM.FORM_RENDERER.DELETE_ROW' | translate"
|
||||
(click)="displayDialogToRemoveRow(currentRootElement.field, rowIndex)"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<section class="adf-grid-list-column-view">
|
||||
@for (column of row.columns; track $index) {
|
||||
<div
|
||||
@@ -100,17 +116,6 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@let shouldDisplayRemoveRowButton = !currentRootElement.field.rows[rowIndex].isInitial || (currentRootElement.field.rows[rowIndex].isInitial && currentRootElement.field.params.allowInitialRowsDelete);
|
||||
@if (shouldDisplayRemoveRowButton) {
|
||||
<button
|
||||
mat-icon-button
|
||||
class="adf-grid-list-remove-row-button"
|
||||
(click)="displayDialogToRemoveRow(currentRootElement.field, rowIndex)"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -98,28 +98,48 @@
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
&-remove-row-button {
|
||||
margin-top: 20px;
|
||||
&-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
#{ms.$mat-icon} {
|
||||
&-remove-button {
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
justify-content: center;
|
||||
|
||||
#{ms.$mat-icon} {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-container {
|
||||
padding: 0 10px;
|
||||
|
||||
&-label {
|
||||
margin: 5px 0 5px -10px;
|
||||
}
|
||||
padding: 0 15px;
|
||||
|
||||
&-multiple {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.54);
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
&.adf-grid-list-container-multiple {
|
||||
border-bottom: 1px solid
|
||||
var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
|
||||
}
|
||||
|
||||
.adf-grid-list-row-label {
|
||||
color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -921,7 +921,7 @@ describe('Form Renderer Component', () => {
|
||||
expect(row).toBeTruthy();
|
||||
|
||||
const removeRowButton = testingUtils.getByCSS(
|
||||
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-remove-row-button'
|
||||
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-row-remove-button'
|
||||
);
|
||||
|
||||
expect(removeRowButton).toBeTruthy();
|
||||
@@ -938,7 +938,7 @@ describe('Form Renderer Component', () => {
|
||||
expect(row).toBeTruthy();
|
||||
|
||||
const removeRowButton = testingUtils.getByCSS(
|
||||
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-remove-row-button'
|
||||
'#field-RepeatableSection0tbw2y-container .adf-grid-list-container .adf-grid-list-row-remove-button'
|
||||
);
|
||||
|
||||
expect(removeRowButton).toBeFalsy();
|
||||
|
||||
@@ -32,6 +32,7 @@ import { FormSectionComponent } from './form-section/form-section.component';
|
||||
import { DecimalRenderMiddlewareService } from './middlewares/decimal-middleware.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ConfirmDialogComponent } from '../../../lib/dialogs/confirm-dialog/confirm.dialog';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-form-renderer',
|
||||
@@ -63,7 +64,8 @@ import { ConfirmDialogComponent } from '../../../lib/dialogs/confirm-dialog/conf
|
||||
NgClass,
|
||||
HeaderWidgetComponent,
|
||||
FormSectionComponent,
|
||||
RepeatWidgetComponent
|
||||
RepeatWidgetComponent,
|
||||
MatTooltipModule
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
|
||||
@@ -1272,61 +1272,121 @@ describe('FormFieldModel', () => {
|
||||
let form: FormModel;
|
||||
let field: FormFieldModel;
|
||||
|
||||
const fields = {
|
||||
'1': [
|
||||
{
|
||||
id: 'Text0wwp7n',
|
||||
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: 'Integer0rzkwq',
|
||||
name: 'Integer',
|
||||
type: 'integer',
|
||||
readOnly: false,
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
placeholder: null,
|
||||
minValue: null,
|
||||
maxValue: null,
|
||||
required: false,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const fieldsDisabled = {
|
||||
'1': [
|
||||
{
|
||||
id: 'Text0wwp7n',
|
||||
name: 'Text',
|
||||
type: 'text',
|
||||
readOnly: true,
|
||||
required: false,
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
placeholder: null,
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
regexPattern: null,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 2
|
||||
}
|
||||
}
|
||||
],
|
||||
'2': [
|
||||
{
|
||||
id: 'Integer0rzkwq',
|
||||
name: 'Integer',
|
||||
type: 'integer',
|
||||
readOnly: false,
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
placeholder: null,
|
||||
minValue: null,
|
||||
maxValue: null,
|
||||
required: false,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const json = {
|
||||
id: 'RepeatableSection0tbw2y',
|
||||
name: 'Repeatable Section',
|
||||
type: 'repeatable-section',
|
||||
tab: null,
|
||||
params: {
|
||||
initialNumberOfRows: 2,
|
||||
allowInitialRowsDelete: true,
|
||||
maxNumberOfRows: 5
|
||||
},
|
||||
numberOfColumns: 2,
|
||||
fields
|
||||
};
|
||||
|
||||
const jsonDisabled = {
|
||||
id: 'RepeatableSection0tbw2y',
|
||||
name: 'Repeatable Section',
|
||||
type: 'repeatable-section',
|
||||
tab: null,
|
||||
params: {
|
||||
initialNumberOfRows: 2,
|
||||
allowInitialRowsDelete: true,
|
||||
maxNumberOfRows: 5
|
||||
},
|
||||
numberOfColumns: 2,
|
||||
fields: fieldsDisabled
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
form = new FormModel();
|
||||
field = new FormFieldModel(form, {
|
||||
id: 'RepeatableSection0tbw2y',
|
||||
name: 'Repeatable Section',
|
||||
type: 'repeatable-section',
|
||||
tab: null,
|
||||
params: {
|
||||
initialNumberOfRows: 2,
|
||||
allowInitialRowsDelete: true,
|
||||
newRowsLimit: 3
|
||||
},
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
'1': [
|
||||
{
|
||||
id: 'Text0wwp7n',
|
||||
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: 'Integer0rzkwq',
|
||||
name: 'Integer',
|
||||
type: 'integer',
|
||||
readOnly: false,
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
placeholder: null,
|
||||
minValue: null,
|
||||
maxValue: null,
|
||||
required: false,
|
||||
visibilityCondition: null,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
field = new FormFieldModel(form, json);
|
||||
});
|
||||
|
||||
describe('add row', () => {
|
||||
@@ -1408,5 +1468,64 @@ describe('FormFieldModel', () => {
|
||||
expect(form.values[field.id]).toEqual(formValues.removeState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled state', () => {
|
||||
const textWidgetId = 'Text0wwp7n';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param expectation expectation function
|
||||
*/
|
||||
function checkChildrenWidgets(expectation: any) {
|
||||
for (const row of field.rows) {
|
||||
for (const column of row.columns) {
|
||||
for (const child of column.fields) {
|
||||
expectation(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
it('should make all children fields disabled if repeatable section is disabled', () => {
|
||||
field.readOnly = true;
|
||||
|
||||
checkChildrenWidgets((child) => {
|
||||
expect(child.readOnly).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow for initial disabled children widgets if repeatable section is enabled', () => {
|
||||
field = new FormFieldModel(form, jsonDisabled);
|
||||
|
||||
checkChildrenWidgets((child) => {
|
||||
if (child.id.startsWith(textWidgetId)) {
|
||||
expect(child.readOnly).toBeTruthy();
|
||||
return;
|
||||
}
|
||||
|
||||
expect(child.readOnly).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep initial disabled children widgets if repeatable section is re-enabled', () => {
|
||||
field = new FormFieldModel(form, jsonDisabled);
|
||||
field.rows[0].columns[0].fields[0].readOnly = true;
|
||||
field.readOnly = true;
|
||||
|
||||
checkChildrenWidgets((child) => {
|
||||
expect(child.readOnly).toBeTruthy();
|
||||
});
|
||||
|
||||
field.readOnly = false;
|
||||
|
||||
checkChildrenWidgets((child) => {
|
||||
if (child.id.startsWith(textWidgetId)) {
|
||||
expect(child.readOnly).toBeTruthy();
|
||||
return;
|
||||
}
|
||||
|
||||
expect(child.readOnly).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -137,6 +137,12 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
|
||||
set readOnly(readOnly: boolean) {
|
||||
this._readOnly = readOnly;
|
||||
|
||||
if (this.type === FormFieldTypes.REPEATABLE_SECTION) {
|
||||
this.updateRepeatableSectionReadOnlyState(readOnly);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateForm();
|
||||
}
|
||||
|
||||
@@ -312,7 +318,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
}
|
||||
|
||||
const col = new ContainerColumnModel();
|
||||
col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field));
|
||||
col.fields = (fields[currentField] || []).map((field: any) => new FormFieldModel(form, field, this.setupParentConfig(field)));
|
||||
col.rowspan = fields[currentField].length;
|
||||
|
||||
if (!FormFieldTypes.isSectionType(this.type)) {
|
||||
@@ -324,6 +330,18 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
});
|
||||
}
|
||||
|
||||
private setupParentConfig(field: any) {
|
||||
if (this.parent) {
|
||||
return {
|
||||
...this.parent,
|
||||
uid: this.getUniqueId(field, this.parent.uid.split(ROW_ID_PREFIX)[1]),
|
||||
value: this.parent.value?.[field.id]
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private repeatableSectionFactory(json: any, form: any): void {
|
||||
const { numberOfColumns = 1, params, value, fields = {} } = json;
|
||||
|
||||
@@ -333,26 +351,26 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
this.colspan = 1;
|
||||
this.rows = [];
|
||||
|
||||
for (let i = 0; i < this.getNumberOfRows(params.initialNumberOfRows, params.newRowsLimit, value); i++) {
|
||||
for (let i = 0; i < this.getNumberOfRows(params.initialNumberOfRows, params.maxNumberOfRows, value); i++) {
|
||||
this.rows.push(this.createRow(fields, form, i, value?.[i], i < params?.initialNumberOfRows));
|
||||
}
|
||||
|
||||
this.columns = this.rows[0].columns;
|
||||
}
|
||||
|
||||
private getNumberOfRows(initialNrRows: number = 1, rowsLimit?: number, value?: any) {
|
||||
return value?.length && !!rowsLimit ? Math.min(value?.length, initialNrRows + rowsLimit) : (value?.length ?? initialNrRows);
|
||||
private getNumberOfRows(initialNrRows: number = 1, maxNrRows?: number, value?: any) {
|
||||
return value?.length ? (maxNrRows ? Math.min(value.length, maxNrRows) : value.length) : initialNrRows;
|
||||
}
|
||||
|
||||
private createRow(fields: any, form: any, index: number, value?: any, isInitial: boolean = false) {
|
||||
const row = new ContainerRowModel(isInitial);
|
||||
|
||||
row.columns.push(...this.createColumns(fields, form, index, value));
|
||||
row.columns.push(...this.createColumns(fields, form, row.id, index, value));
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private createColumns(fields: any, form: any, index?: number, value?: any) {
|
||||
private createColumns(fields: any, form: any, rowId: string, index?: number, value?: any) {
|
||||
const columns: ContainerColumnModel[] = [];
|
||||
|
||||
Object.keys(fields).forEach((currentField) => {
|
||||
@@ -365,10 +383,10 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
(field: any) =>
|
||||
new FormFieldModel(form, field, {
|
||||
id: this.id,
|
||||
uid: this.getUniqueId(field),
|
||||
uid: this.getUniqueId(field, rowId),
|
||||
fields: this.fields,
|
||||
rowIndex: index ?? 0,
|
||||
value: value?.[field.id]
|
||||
value: field.type === FormFieldTypes.SECTION ? value : value?.[field.id]
|
||||
})
|
||||
);
|
||||
col.rowspan = fields[currentField].length;
|
||||
@@ -384,8 +402,34 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
return columns;
|
||||
}
|
||||
|
||||
private getUniqueId(field: FormFieldModel): string {
|
||||
return field.id + ROW_ID_PREFIX + window.crypto.getRandomValues(new Uint32Array(1))[0].toString();
|
||||
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);
|
||||
}
|
||||
|
||||
field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateInnerSectionReadOnlyState(section: FormFieldModel, state: boolean) {
|
||||
for (const column of section.columns) {
|
||||
for (const field of column.fields) {
|
||||
field.readOnly = this.getRepeatableSectionFieldReadOnlyState(field, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getRepeatableSectionFieldReadOnlyState(field: FormFieldModel, state: boolean): boolean {
|
||||
return state || field.json.readOnly;
|
||||
}
|
||||
|
||||
private getUniqueId(field: FormFieldModel, rowId: string): string {
|
||||
return field.id + ROW_ID_PREFIX + rowId;
|
||||
}
|
||||
|
||||
private updateChildrenFieldsRowIndex() {
|
||||
@@ -400,11 +444,23 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
|
||||
private createInitialValue(fields: any) {
|
||||
return Object.keys(fields)
|
||||
.map((currentField) => (fields[currentField] || []).map((field) => field.id))
|
||||
.flat(1)
|
||||
.map((currentField) => (fields[currentField] || []).map((field) => this.getFieldId(field)))
|
||||
.flat(2)
|
||||
.reduce((acc, curr) => ((acc[curr] = null), acc), {});
|
||||
}
|
||||
|
||||
private getFieldId(field: any) {
|
||||
if (field.type === FormFieldTypes.SECTION) {
|
||||
const fields = field.fields;
|
||||
|
||||
return Object.keys(fields)
|
||||
.map((currentField) => (fields[currentField] || []).map((e) => e.id))
|
||||
.flat(1);
|
||||
}
|
||||
|
||||
return field.id;
|
||||
}
|
||||
|
||||
private updateContainerColspan(fields: FormFieldModel[]): void {
|
||||
fields.forEach((colField: FormFieldModel) => {
|
||||
this.colspan = Math.max(this.colspan, colField.colspan);
|
||||
@@ -420,7 +476,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
}
|
||||
|
||||
private shouldAddRow(): boolean {
|
||||
return !this.params.newRowsLimit || this.rows.length < this.params.initialNumberOfRows + this.params.newRowsLimit;
|
||||
return !this.params.maxNumberOfRows || this.rows.length < this.params.maxNumberOfRows;
|
||||
}
|
||||
|
||||
removeRow(index: number) {
|
||||
@@ -526,7 +582,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
}
|
||||
|
||||
if (this.isCheckboxField(json)) {
|
||||
return json.value === 'true' || json.value === true;
|
||||
return json.value === 'true' || json.value === true || initialValue === true || initialValue === 'true';
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -537,16 +593,22 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
return;
|
||||
}
|
||||
|
||||
const formValue = this.getFormValue();
|
||||
|
||||
if (this.parent) {
|
||||
this.updateRepeatableSectionValue();
|
||||
return;
|
||||
this.updateRepeatableSectionValue(formValue);
|
||||
} else {
|
||||
this.updateValue(formValue);
|
||||
}
|
||||
|
||||
this.form.onFormFieldChanged(this);
|
||||
}
|
||||
|
||||
getFormValue() {
|
||||
switch (this.type) {
|
||||
case FormFieldTypes.DROPDOWN: {
|
||||
if (!this.value) {
|
||||
this.form.values[this.id] = null;
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -554,63 +616,57 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
but saving back as object: { id: <id>, name: <name> }
|
||||
*/
|
||||
if (Array.isArray(this.value)) {
|
||||
this.form.values[this.id] = this.value;
|
||||
break;
|
||||
return this.value;
|
||||
}
|
||||
|
||||
if (typeof this.value === 'string') {
|
||||
if (this.value === 'empty' || this.value === '') {
|
||||
this.form.values[this.id] = null;
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchingOption: FormFieldOption = this.options.find((opt) => opt.id === this.value);
|
||||
|
||||
this.form.values[this.id] = matchingOption || null;
|
||||
return matchingOption || null;
|
||||
}
|
||||
|
||||
if (typeof this.value === 'object') {
|
||||
if (this.value.id === 'empty' || this.value.id === '') {
|
||||
this.form.values[this.id] = null;
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchingOption: FormFieldOption = this.options.find((opt) => opt.id === this.value.id);
|
||||
|
||||
this.form.values[this.id] = matchingOption;
|
||||
return matchingOption;
|
||||
}
|
||||
break;
|
||||
|
||||
return null;
|
||||
}
|
||||
case FormFieldTypes.RADIO_BUTTONS: {
|
||||
const radioButton: FormFieldOption = this.options.find((opt) => opt.id === this.value);
|
||||
|
||||
if (this.optionType === 'rest') {
|
||||
this.form.values[this.id] = radioButton
|
||||
? { ...radioButton, options: this.options }
|
||||
: { id: null, name: null, options: this.options };
|
||||
} else {
|
||||
this.form.values[this.id] = radioButton ? { ...radioButton } : null;
|
||||
return radioButton ? { ...radioButton, options: this.options } : { id: null, name: null, options: this.options };
|
||||
}
|
||||
|
||||
break;
|
||||
return radioButton ? { ...radioButton } : null;
|
||||
}
|
||||
case FormFieldTypes.UPLOAD: {
|
||||
this.form.hasUpload = true;
|
||||
|
||||
if (this.value && this.value.length > 0) {
|
||||
this.form.values[this.id] = Array.isArray(this.value) ? this.value.map((elem) => elem.id).join(',') : [this.value];
|
||||
} else {
|
||||
this.form.values[this.id] = null;
|
||||
return Array.isArray(this.value) ? this.value.map((elem) => elem.id).join(',') : [this.value];
|
||||
}
|
||||
break;
|
||||
|
||||
return null;
|
||||
}
|
||||
case FormFieldTypes.TYPEAHEAD: {
|
||||
const typeAheadEntry: FormFieldOption[] = this.options.filter((opt) => opt.id === this.value || opt.name === this.value);
|
||||
|
||||
if (typeAheadEntry.length > 0) {
|
||||
this.form.values[this.id] = typeAheadEntry[0];
|
||||
} else if (this.options.length > 0) {
|
||||
this.form.values[this.id] = null;
|
||||
return typeAheadEntry[0];
|
||||
}
|
||||
break;
|
||||
|
||||
return null;
|
||||
}
|
||||
case FormFieldTypes.DATE: {
|
||||
if (typeof this.value === 'string' && this.value === 'today') {
|
||||
@@ -618,6 +674,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
}
|
||||
|
||||
let dateValue;
|
||||
|
||||
try {
|
||||
dateValue = DateFnsUtils.parseDate(this.value, this.dateDisplayFormat);
|
||||
} catch {
|
||||
@@ -626,12 +683,13 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
|
||||
if (isValidDate(dateValue)) {
|
||||
const datePart = DateFnsUtils.formatDate(dateValue, 'yyyy-MM-dd');
|
||||
this.form.values[this.id] = `${datePart}T00:00:00.000Z`;
|
||||
} else {
|
||||
this.form.values[this.id] = null;
|
||||
this._value = this.value;
|
||||
|
||||
return `${datePart}T00:00:00.000Z`;
|
||||
}
|
||||
break;
|
||||
|
||||
this._value = this.value;
|
||||
|
||||
return null;
|
||||
}
|
||||
case FormFieldTypes.DATETIME: {
|
||||
if (typeof this.value === 'string' && this.value === 'now') {
|
||||
@@ -641,39 +699,32 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
const dateTimeValue = this.value !== null ? DateFnsUtils.getDate(this.value) : null;
|
||||
|
||||
if (isValidDate(dateTimeValue)) {
|
||||
this.form.values[this.id] = dateTimeValue.toISOString();
|
||||
} else {
|
||||
this.form.values[this.id] = null;
|
||||
this._value = this.value;
|
||||
return dateTimeValue.toISOString();
|
||||
}
|
||||
break;
|
||||
|
||||
this._value = this.value;
|
||||
|
||||
return null;
|
||||
}
|
||||
case FormFieldTypes.NUMBER: {
|
||||
this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
|
||||
break;
|
||||
return this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
|
||||
}
|
||||
case FormFieldTypes.AMOUNT: {
|
||||
this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
|
||||
break;
|
||||
return this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10);
|
||||
}
|
||||
case FormFieldTypes.DECIMAL: {
|
||||
this.form.values[this.id] = parseFloat(this.value);
|
||||
break;
|
||||
return parseFloat(this.value);
|
||||
}
|
||||
case FormFieldTypes.BOOLEAN: {
|
||||
this.form.values[this.id] = this.value !== null && this.value !== undefined ? this.value : false;
|
||||
break;
|
||||
return this.value !== null && this.value !== undefined ? this.value : false;
|
||||
}
|
||||
case FormFieldTypes.PEOPLE: {
|
||||
this.form.values[this.id] = this.value ? this.value : null;
|
||||
break;
|
||||
return this.value ? this.value : null;
|
||||
}
|
||||
case FormFieldTypes.FUNCTIONAL_GROUP: {
|
||||
this.form.values[this.id] = this.value ? this.value : null;
|
||||
break;
|
||||
return this.value ? this.value : null;
|
||||
}
|
||||
case FormFieldTypes.REPEATABLE_SECTION: {
|
||||
this.form.values[this.id] = this.value ? this.value : [];
|
||||
this.repeatableSectionFactory(
|
||||
{
|
||||
...this.json,
|
||||
@@ -681,18 +732,31 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
},
|
||||
this.form
|
||||
);
|
||||
break;
|
||||
|
||||
return this.value ? this.value : this.form.values[this.id];
|
||||
}
|
||||
default:
|
||||
if (this.shouldUpdateFormValues(this.type)) {
|
||||
this.form.values[this.id] = this.value;
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
this.form.onFormFieldChanged(this);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private updateRepeatableSectionValue() {
|
||||
private updateValue(value: any) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.form.values[this.id] = value;
|
||||
}
|
||||
|
||||
private updateRepeatableSectionValue(value: string) {
|
||||
if (this.type === FormFieldTypes.SECTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.form.values[this.parent.id]) {
|
||||
this.form.values[this.parent.id] = [];
|
||||
}
|
||||
@@ -701,9 +765,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
this.form.values[this.parent.id][this.parent.rowIndex] = this.createInitialValue(this.parent.fields);
|
||||
}
|
||||
|
||||
this.form.values[this.parent.id][this.parent.rowIndex][this.id.split(ROW_ID_PREFIX)[0]] = this.value;
|
||||
|
||||
this.form.onFormFieldChanged(this);
|
||||
this.form.values[this.parent.id][this.parent.rowIndex][this.id.split(ROW_ID_PREFIX)[0]] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div [style]="element | adfFieldStyle" class="adf-container-widget-repeat">
|
||||
<div [style]="element | adfFieldStyle" class="adf-container-widget-repeat" [class.adf-readonly]="element.field.readOnly">
|
||||
<h4
|
||||
id="container-repeat"
|
||||
class="adf-container-widget-repeat__text"
|
||||
@@ -11,19 +11,19 @@
|
||||
<ng-content />
|
||||
|
||||
@if (!isEditor) {
|
||||
@let shouldDisplayAddRowButton = !element.field.params.newRowsLimit || (element.field.params.newRowsLimit > getAddedRowsCount());
|
||||
@let shouldDisplayAddRowButton = !element.field.params.maxNumberOfRows || (element.field.params.maxNumberOfRows > element.field.rows.length);
|
||||
@if (shouldDisplayAddRowButton) {
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
class="adf-container-widget-row-action"
|
||||
[disabled]="element.field.readOnly"
|
||||
(click)="addRow()"
|
||||
>
|
||||
<mat-icon>add</mat-icon> {{ 'FORM.FIELD.REPEATABLE_SECTION.ADD_ROW' | translate }}
|
||||
</button>
|
||||
} @else {
|
||||
@let rowLimit = element.field.params.initialNumberOfRows + (element.field.params.newRowsLimit ?? 0);
|
||||
<span class="adf-container-widget-row-action adf-container-widget-row-limit">{{ 'FORM.FIELD.REPEATABLE_SECTION.ROW_LIMIT_REACHED' | translate: { limit: rowLimit } }}</span>
|
||||
<span class="adf-container-widget-row-action adf-container-widget-row-limit">{{ 'FORM.FIELD.REPEATABLE_SECTION.ROW_LIMIT_REACHED' | translate: { limit: element.field.params.maxNumberOfRows } }}</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
font-weight: var(--adf-header-font-weight);
|
||||
color: var(--adf-header-color);
|
||||
line-height: normal;
|
||||
|
||||
&.adf-collapsible {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-row-action {
|
||||
@@ -23,3 +19,18 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-readonly {
|
||||
.adf-container-widget {
|
||||
&-repeat__text {
|
||||
color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
|
||||
border-bottom: 1px solid var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&-row-limit {
|
||||
color: var(--mdc-text-button-disabled-label-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent));
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@ describe('RepeatWidgetComponent', () => {
|
||||
/**
|
||||
*
|
||||
* @param initialNumberOfRows initial number of rows
|
||||
* @param newRowsLimit limit for additional rows
|
||||
* @param maxNumberOfRows maximum number of rows
|
||||
* @param allowInitialRowsDelete should allow deleting rows
|
||||
* @returns repeatable section json based on params
|
||||
*/
|
||||
function getFormFieldJson(initialNumberOfRows: number = 2, newRowsLimit?: number, allowInitialRowsDelete: boolean = true) {
|
||||
function getFormFieldJson(initialNumberOfRows: number = 2, maxNumberOfRows?: number, allowInitialRowsDelete: boolean = true) {
|
||||
return {
|
||||
id: 'RepeatableSection0tbw2y',
|
||||
name: 'Repeatable Section',
|
||||
@@ -43,7 +43,7 @@ describe('RepeatWidgetComponent', () => {
|
||||
params: {
|
||||
initialNumberOfRows,
|
||||
allowInitialRowsDelete,
|
||||
newRowsLimit
|
||||
maxNumberOfRows
|
||||
},
|
||||
numberOfColumns: 2,
|
||||
fields: {
|
||||
@@ -126,7 +126,7 @@ describe('RepeatWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should display add row button if limit is defined but not reached', () => {
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 3)));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -135,8 +135,7 @@ describe('RepeatWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should NOT display add row button if limit is defined and reached', () => {
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
|
||||
spyOn(component, 'getAddedRowsCount').and.returnValue(1);
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 2)));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -145,8 +144,7 @@ describe('RepeatWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should display row limit if limit has been reached', () => {
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
|
||||
spyOn(component, 'getAddedRowsCount').and.returnValue(1);
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 2)));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -165,31 +163,5 @@ describe('RepeatWidgetComponent', () => {
|
||||
testingUtils.clickByCSS('button.adf-container-widget-row-action');
|
||||
expect(component.addRow).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('getAddedRowsCount', () => {
|
||||
it('should get correct rows count if initial rows are allowed to be deleted', () => {
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1)));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.getAddedRowsCount()).toBe(0);
|
||||
|
||||
component.addRow();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.getAddedRowsCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should get correct rows count if initial rows are NOT allowed to be deleted', () => {
|
||||
component.element = new ContainerModel(new FormFieldModel(new FormModel(), getFormFieldJson(2, 1, false)));
|
||||
|
||||
expect(component.getAddedRowsCount()).toBe(0);
|
||||
|
||||
component.addRow();
|
||||
|
||||
expect(component.getAddedRowsCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,10 +36,4 @@ export class RepeatWidgetComponent {
|
||||
addRow() {
|
||||
this.element.field.addRow(this.element.json.fields, this.element.form);
|
||||
}
|
||||
|
||||
getAddedRowsCount(): number {
|
||||
return this.element.json.params.allowInitialRowsDelete
|
||||
? this.element.field.rows.length - this.element.json.params.initialNumberOfRows
|
||||
: this.element.field.rows.filter((row) => !row.isInitial).length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"FORM_RENDERER": {
|
||||
"NAMELESS_TASK": "Nameless task",
|
||||
"ROW_LABEL": "Row {{ number }}",
|
||||
"DELETE_ROW": "Delete row",
|
||||
"REMOVE_ROW_DIALOG": {
|
||||
"TITLE": "Delete the row",
|
||||
"MESSAGE": "Are you sure you want to delete this row?",
|
||||
|
||||
Reference in New Issue
Block a user