From a748541ca0882291071834a0ebedf5a4deed2e47 Mon Sep 17 00:00:00 2001 From: Diogo Bastos <50139916+DiogoABastos@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:50:44 +0000 Subject: [PATCH] AAE-39988 Fix dropdown conditional functionality (#11343) * AAE-39988 Fix dropdown conditional functionality * AAE-39988 Add form-field-rule handler --- .../widgets/core/form-field.model.spec.ts | 117 ++++++++++++++++++ .../widgets/core/form-field.model.ts | 20 ++- .../handlers/form-field-rule.handler.spec.ts | 69 +++++++++++ .../core/handlers/form-field-rule.handler.ts | 39 ++++++ .../widgets/core/repeatable-section.model.ts | 28 +++++ 5 files changed, 261 insertions(+), 12 deletions(-) create mode 100644 lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.spec.ts create mode 100644 lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.ts create mode 100644 lib/core/src/lib/form/components/widgets/core/repeatable-section.model.ts diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts index 2a77603a43..8d963f45e0 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.spec.ts @@ -1291,6 +1291,38 @@ describe('FormFieldModel', () => { existingColspan: 1, maxColspan: 2 } + }, + { + id: 'Dropdown0e7tn4', + name: 'Dropdown', + type: 'dropdown', + readOnly: false, + required: false, + colspan: 1, + rowspan: 1, + optionType: 'manual', + options: [ + { + id: 'Id_1', + name: 'Label 1' + }, + { + id: 'Id_2', + name: 'Label 2' + } + ], + authName: null, + restUrl: null, + restResponsePath: null, + restIdProperty: null, + restLabelProperty: null, + selectionType: 'single', + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + }, + rule: null } ], '2': [ @@ -1310,6 +1342,50 @@ describe('FormFieldModel', () => { existingColspan: 1, maxColspan: 2 } + }, + { + id: 'Dropdown0wgm63', + name: 'Dropdown', + type: 'dropdown', + readOnly: false, + required: false, + colspan: 1, + rowspan: 1, + optionType: 'manual', + options: [], + authName: null, + restUrl: null, + restResponsePath: null, + restIdProperty: null, + restLabelProperty: null, + selectionType: 'single', + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + }, + rule: { + ruleOn: 'Dropdown0e7tn4', + entries: [ + { + key: 'Id_1', + options: [ + { + id: 'Id_3', + name: 'Label 3' + }, + { + id: 'Id_4', + name: 'Label 4' + } + ] + }, + { + key: 'Id_2', + options: [] + } + ] + } } ] }; @@ -1544,5 +1620,46 @@ describe('FormFieldModel', () => { }); }); }); + + describe('widgets', () => { + describe('id property', () => { + let textWidget: FormFieldModel; + + beforeEach(() => { + textWidget = field.rows[0].columns[0].fields[0]; + }); + + it('should set id to parent uid', () => { + expect(textWidget.id).toBe(textWidget.parent.uid); + }); + + it('should NOT set id to json id', () => { + const textWidgetJson = json.fields['1'][0]; + + expect(textWidget.id).not.toBe(textWidgetJson.id); + }); + }); + + describe('rule property', () => { + let conditionalDropdown: FormFieldModel; + let targetDropdown: FormFieldModel; + + beforeEach(() => { + conditionalDropdown = field.rows[0].columns[1].fields[1]; + }); + + it('should set ruleOn property of rule to target id', () => { + targetDropdown = field.rows[0].columns[0].fields[1]; + + expect(conditionalDropdown.rule.ruleOn).toBe(targetDropdown.id); + }); + + it('should NOT set ruleOn property of rule to target json id', () => { + const targetDropdownJson = json.fields['1'][1]; + + expect(conditionalDropdown.rule.ruleOn).not.toBe(targetDropdownJson.id); + }); + }); + }); }); }); diff --git a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts index f4618de844..cacbd6009a 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field.model.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field.model.ts @@ -31,21 +31,13 @@ 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 { formFieldRuleHandler } from './handlers/form-field-rule.handler'; export type FieldOptionType = 'rest' | 'manual' | 'variable'; export type FieldSelectionType = 'single' | 'multiple'; export type FieldAlignmentType = 'vertical' | 'horizontal'; -interface RepeatableSectionModel { - id: string; - uid: string; - fields: FormFieldModel[]; - rowIndex: number; - value?: any; -} - -const ROW_ID_PREFIX = '-Row'; - // Maps to FormFieldRepresentation export class FormFieldModel extends FormWidgetModel { private _value: string; @@ -195,7 +187,7 @@ export class FormFieldModel extends FormWidgetModel { super(form, json); if (json) { this.fieldType = json.fieldType; - this.id = parent ? parent.uid : json.id; + this.id = this.getId(json.id, parent); this.name = json.name; this.type = json.type; this.roles = json.roles; @@ -233,7 +225,7 @@ export class FormFieldModel extends FormWidgetModel { this.tooltip = json.tooltip || ''; this.selectionType = json.selectionType; this.alignmentType = json.alignmentType; - this.rule = json.rule; + this.rule = formFieldRuleHandler.getRule(this.id, json.rule, parent); this.selectLoggedUser = json.selectLoggedUser; this.groupsRestriction = json.groupsRestriction?.groups; this.variableConfig = json.variableConfig; @@ -289,6 +281,10 @@ export class FormFieldModel extends FormWidgetModel { return originalType === FormFieldTypes.DATETIME ? this.defaultDateTimeFormat : this.defaultDateFormat; } + private getId(id: string, parent?: RepeatableSectionModel): string { + return parent ? parent.uid : id; + } + private isTypeaheadFieldType(type: string): boolean { return type === 'typeahead'; } diff --git a/lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.spec.ts b/lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.spec.ts new file mode 100644 index 0000000000..2874881685 --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.spec.ts @@ -0,0 +1,69 @@ +/*! + * @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 { formFieldRuleHandler } from './form-field-rule.handler'; +import { FormFieldRule } from '../form-field-rule'; +import { RepeatableSectionModel } from '../repeatable-section.model'; + +describe('formFieldRuleHandler', () => { + it('should return null if provided rule is null', () => { + expect(formFieldRuleHandler.getRule('mock-id', null)).toBe(null); + }); + + it('should return undefined if provided rule is undefined', () => { + expect(formFieldRuleHandler.getRule('mock-id', undefined)).toBe(undefined); + }); + + it('should return provided rule if rule is provided', () => { + const rule: FormFieldRule = { + ruleOn: 'mock-rule-on', + entries: [] + }; + + expect(formFieldRuleHandler.getRule('mock-id', rule)).toEqual(rule); + }); + + it('should return provided rule if rule is provided and no parent is provided', () => { + const rule: FormFieldRule = { + ruleOn: 'mock-rule-on', + entries: [] + }; + + expect(formFieldRuleHandler.getRule('mock-id', rule)).toEqual(rule); + }); + + it('should return rule with parent ruleOn property if rule and parent are provided', () => { + const rule: FormFieldRule = { + ruleOn: 'mock-rule-on', + entries: [] + }; + + const parent: RepeatableSectionModel = { + id: 'mock-parent-id', + uid: 'mock-id-Row123456789', + fields: [], + rowIndex: 0 + }; + + const expectedRule: FormFieldRule = { + ruleOn: 'mock-rule-on-Row123456789', + entries: [] + }; + + expect(formFieldRuleHandler.getRule('mock-id-Row123456789', rule, parent)).toEqual(expectedRule); + }); +}); diff --git a/lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.ts b/lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.ts new file mode 100644 index 0000000000..be191c1dbf --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/core/handlers/form-field-rule.handler.ts @@ -0,0 +1,39 @@ +/*! + * @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 { FormFieldRule } from '../form-field-rule'; +import { RepeatableSectionModel, ROW_ID_PREFIX } from '../repeatable-section.model'; + +const getRule = (id: string, rule?: FormFieldRule, parent?: RepeatableSectionModel): FormFieldRule => + rule && parent + ? ({ + ...rule, + ruleOn: getRepeatableSectionChildRuleOn(id, rule.ruleOn ?? '') + } as FormFieldRule) + : rule; + +const getRepeatableSectionChildRuleOn = (id: string, ruleOn: string): string => ruleOn + ROW_ID_PREFIX + getRowId(id); + +const getRowId = (id: string) => { + const split = id.split(ROW_ID_PREFIX); + + return split.length > 1 ? split[1] : ''; +}; + +export const formFieldRuleHandler = { + getRule +}; diff --git a/lib/core/src/lib/form/components/widgets/core/repeatable-section.model.ts b/lib/core/src/lib/form/components/widgets/core/repeatable-section.model.ts new file mode 100644 index 0000000000..861c164a57 --- /dev/null +++ b/lib/core/src/lib/form/components/widgets/core/repeatable-section.model.ts @@ -0,0 +1,28 @@ +/*! + * @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 { FormFieldModel } from './form-field.model'; + +export const ROW_ID_PREFIX = '-Row'; + +export interface RepeatableSectionModel { + id: string; + uid: string; + fields: FormFieldModel[]; + rowIndex: number; + value?: any; +}