diff --git a/docs/displaying-dependencies.md b/docs/displaying-dependencies.md new file mode 100644 index 0000000..93dd654 --- /dev/null +++ b/docs/displaying-dependencies.md @@ -0,0 +1,256 @@ +# Displaying Dependencies Form Fields Configuration + +The Form widget supports a **`displayingDependencies`** configuration that allows fields to be dynamically shown or hidden depending on the value of another field. + +This feature is useful when building progressive or guided forms, where some fields should only appear after: +- a parent field has been filled, +- a switch has been enabled, +- a specific option has been selected, +- or a field matches a precise value. + +The goal is to reduce visual noise and improve the user experience by displaying only the fields that are currently relevant. + +Example of Form before inserting values: + +![displaying dependencies before](./img/displaying-dependencies-before.png) + +Example of Form after inserting values: + +![displaying dependencies after](./img/displaying-dependencies-after.png) + +--- + +## Displaying Dependencies + +### Description + +The `displayingDependencies` property defines visibility rules between fields. + +Each item describes: +- the field to display (`name`) +- the field it depends on (`dependsOn.name`) +- the visibility condition (`conditionType`) +- optionally, the expected value (`value`) + +Notes: +- Hidden fields are not rendered. +- Visibility updates automatically when dependency fields change. +- Multiple fields can depend on the same parent field. +- Dependencies can target nested fields using dot notation. +- `displayingDependencies` only controls visibility and does not affect validation rules directly. + +--- + +## Supported Conditions + +### `notEmpty` + +Displays the target field when the dependency field contains a non-empty value. + +Supported checks: +- non-empty strings +- non-empty arrays +- non-null values +- booleans +- numbers + +### `value` + +Displays the target field only when the dependency field matches a specific value. + +Supported value types: +- `string` +- `integer` +- `boolean` +- `array` +- `option` +- `null` + +--- + +## Example (YAML) + +```yaml +displayingDependencies: + - name: surname + dependsOn: + name: name + conditionType: notEmpty + + - name: middleName + dependsOn: + name: name + conditionType: notEmpty + + - name: province + dependsOn: + name: region + conditionType: value + value: + type: option + optionValue: + value: liguria +``` + +--- + +## Example Behavior + +In this example: + +- `surname` and `middleName` are displayed only after the `name` field has been filled. +- `province` is displayed only when the `region` field matches the value `liguria`. + +This allows forms to progressively reveal additional fields only when they become relevant. + +--- + +## Complete Example + +```yaml +kind: Form +apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: example-form-displaying-dependencies + namespace: krateo-system + +spec: + widgetData: + displayingDependencies: + - name: surname + dependsOn: + name: name + conditionType: notEmpty + + - name: middleName + dependsOn: + name: name + conditionType: notEmpty + + - name: province + dependsOn: + name: region + conditionType: value + value: + type: option + optionValue: + value: liguria + + stringSchema: | + { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Fill this field to display dependent fields" + }, + + "middleName": { + "type": "string", + "title": "Middle Name" + }, + + "surname": { + "type": "string", + "title": "Surname" + }, + + "region": { + "type": "string", + "title": "Region", + "enum": ["lombardy", "liguria", "veneto"] + }, + + "province": { + "type": "string", + "title": "Province" + } + } + } +``` + +--- + +## Supported Value Configurations + +### String + +```yaml +dependsOn: + name: typeField + conditionType: value + value: + type: string + stringValue: test +``` + +### Integer + +```yaml +dependsOn: + name: typeField + conditionType: value + value: + type: integer + integerValue: 2 +``` + +### Boolean + +```yaml +dependsOn: + name: typeField + conditionType: value + value: + type: boolean + booleanValue: true +``` + +### Array + +```yaml +dependsOn: + name: tags + conditionType: value + value: + type: array + arrayValue: ['frontend', 'backend'] +``` + +### Option + +Useful for fields using options (such as fields configured using the `autocomplete` or `dependencies` properties). + +```yaml +dependsOn: + name: region + conditionType: value + value: + type: option + optionValue: + value: liguria +``` + +You can also match both `value` and `label`: + +```yaml +dependsOn: + name: region + conditionType: value + value: + type: option + optionValue: + value: "7" + label: "Liguria" +``` + +### Null + +```yaml +dependsOn: + name: optionalField + conditionType: value + value: + type: null +``` \ No newline at end of file diff --git a/docs/img/displaying-dependencies-after.png b/docs/img/displaying-dependencies-after.png new file mode 100644 index 0000000..2d1884b Binary files /dev/null and b/docs/img/displaying-dependencies-after.png differ diff --git a/docs/img/displaying-dependencies-before.png b/docs/img/displaying-dependencies-before.png new file mode 100644 index 0000000..5321f38 Binary files /dev/null and b/docs/img/displaying-dependencies-before.png differ diff --git a/src/examples/widgets/Form/Form.example.yaml b/src/examples/widgets/Form/Form.example.yaml index 2b4000b..e87a2d7 100644 --- a/src/examples/widgets/Form/Form.example.yaml +++ b/src/examples/widgets/Form/Form.example.yaml @@ -1444,6 +1444,123 @@ spec: resource: restactions verb: GET --- +# Displaying dependencies in nested fields +# Target: validate fields whose displaying depend on the value of another field with nested fields +# Expected behavior: initially one field is rendered, with the following rendered only if the previous one is compiled +kind: Form +apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: example-form-nested-displaying-dependencies + namespace: krateo-system +spec: + widgetData: + displayingDependencies: + - name: user.surname + dependsOn: + name: user.name + conditionType: notEmpty + - name: user.details.nickname + dependsOn: + name: user.surname + conditionType: notEmpty + - name: user.details.secretCode + dependsOn: + name: user.details.isAdmin + conditionType: value + value: + type: boolean + booleanValue: true + - name: user.address.province + dependsOn: + name: user.address.region + conditionType: value + value: + type: option + optionValue: + value: liguria + submitActionId: submit-autocomplete + stringSchema: | + { + "type": "object", + "properties": { + "user": { + "type": "object", + "title": "User", + + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Fill this field to display 'Surname'" + }, + + "surname": { + "type": "string", + "title": "Surname", + "description": "Fill this field to display nested field 'Nickname'" + }, + + "details": { + "type": "object", + "title": "Details", + + "properties": { + "nickname": { + "type": "string", + "title": "Nickname" + }, + + "isAdmin": { + "type": "boolean", + "title": "Administrator", + "description": "Enable this switch to display 'Secret Code'" + }, + + "secretCode": { + "type": "string", + "title": "Secret Code" + } + } + }, + + "address": { + "type": "object", + "title": "Address", + + "properties": { + "region": { + "type": "string", + "title": "Region", + "enum": ["lombardy", "liguria", "veneto"], + "description": "Select 'liguria' to display the province field" + }, + + "province": { + "type": "string", + "title": "Province" + } + } + } + } + } + } + } + + actions: + rest: + - id: submit-example + type: rest + resourceRefId: resource-ref-1 + headers: [] + resourcesRefs: + items: + - id: resource-ref-1 + apiVersion: composition.krateo.io/v1 + name: submit-example + namespace: krateo-system + resource: fireworksapps + verb: POST +--- # Missing action # Target: validate error handling when action is missing. # Expected behavior: an error is displayed on submit to inform that the action is not defined. diff --git a/src/examples/widgets/Form/Form.menu.yaml b/src/examples/widgets/Form/Form.menu.yaml index 8020a83..5c6c9d6 100644 --- a/src/examples/widgets/Form/Form.menu.yaml +++ b/src/examples/widgets/Form/Form.menu.yaml @@ -101,6 +101,7 @@ spec: - resourceRefId: example-form-dependencies-panel - resourceRefId: example-form-autocomplete-dependencies-array-objects-panel - resourceRefId: example-form-displaying-dependencies-panel + - resourceRefId: example-form-nested-displaying-dependencies-panel resourcesRefs: items: - id: example-form-missing-schema-panel @@ -235,6 +236,12 @@ spec: namespace: krateo-system resource: panels verb: GET + - id: example-form-nested-displaying-dependencies-panel + apiVersion: widgets.templates.krateo.io/v1beta1 + name: example-form-nested-displaying-dependencies-panel + namespace: krateo-system + resource: panels + verb: GET --- kind: Column apiVersion: widgets.templates.krateo.io/v1beta1 @@ -823,6 +830,26 @@ spec: --- kind: Panel apiVersion: widgets.templates.krateo.io/v1beta1 +metadata: + name: example-form-nested-displaying-dependencies-panel + namespace: krateo-system +spec: + widgetData: + actions: {} + title: 'Displaying dependencies in nested fields' + items: + - resourceRefId: example-form-nested-displaying-dependencies + resourcesRefs: + items: + - id: example-form-nested-displaying-dependencies + apiVersion: widgets.templates.krateo.io/v1beta1 + name: example-form-nested-displaying-dependencies + namespace: krateo-system + resource: forms + verb: GET +--- +kind: Panel +apiVersion: widgets.templates.krateo.io/v1beta1 metadata: name: example-form-missing-action-panel namespace: krateo-system diff --git a/src/hooks/useWidgetQuery.ts b/src/hooks/useWidgetQuery.ts index 1675726..c460f44 100644 --- a/src/hooks/useWidgetQuery.ts +++ b/src/hooks/useWidgetQuery.ts @@ -90,7 +90,7 @@ export const useWidgetQuery = (widgetEndpoint: string, options?: { enabled: bool const queryResult = useInfiniteQuery({ enabled: enabledFlag, - queryKey: ['widgets', widgetEndpoint], + queryKey: ['widgets', widgetEndpoint, fetchWidget], queryFn: ({ pageParam }) => fetchWidget(pageParam), initialPageParam: isCursorPagination ? { cursor: initialCursor, perPage: initialPerPage || 10 }