diff --git a/docs/guides/building-custom-apps-v2.md b/docs/guides/building-custom-apps-v2.md index ccab5d0..e43588a 100644 --- a/docs/guides/building-custom-apps-v2.md +++ b/docs/guides/building-custom-apps-v2.md @@ -40,6 +40,8 @@ In ODE terms: - Follow-ups include a reference to the original (e.g., `bean: "Prainema"`) - The app queries and displays related data +Some projects instead embed related **child payloads** **inside** the parent observation using **`format: sub-observation`** (a JSON array on one observation). That pattern is documented under [Custom Extensions](./custom-extensions.md#sub-observations-format-sub-observation); this guide focuses on **separate** follow-up observations linked by queries and IDs. + --- ## Part 1: Create the follow-up form diff --git a/docs/guides/building-custom-apps.md b/docs/guides/building-custom-apps.md index 943319f..d64ea3b 100644 --- a/docs/guides/building-custom-apps.md +++ b/docs/guides/building-custom-apps.md @@ -46,7 +46,7 @@ Use custom_apps when you need: Custom apps use **JSONForms** extended with ODE-specific question types: - Standard types: text, number, date, dropdown, radio, checkbox -- ODE extensions: photo, GPS, QR code, signature, and more +- ODE extensions: photo, GPS, QR code, signature, **sub-observations** (embedded child payloads as JSON arrays — see [Custom Extensions](./custom-extensions.md#sub-observations-format-sub-observation)), and more ### Data relationships diff --git a/docs/guides/custom-extensions.md b/docs/guides/custom-extensions.md index 0ad845f..fed681d 100644 --- a/docs/guides/custom-extensions.md +++ b/docs/guides/custom-extensions.md @@ -19,6 +19,62 @@ The extension system enables you to: - **Reusable Components** - Package extensions for distribution to other implementations - **Automatic Distribution** - Deploy via app bundles; users get updates automatically +## Sub-observations (`format: sub-observation`) + +:::tip Built-in Formplayer control +Sub-observations are rendered by **Formplayer itself**. You declare them on the parent form’s JSON Schema. +::: + +Use sub-observations when related answers should live **inside the parent observation** as an **embedded JSON array** of child payloads, instead of separate top-level observations per child. + +### Behavior + +- The parent schema defines one property (often `type: "array"`) with `"format": "sub-observation"` plus the configuration keys below. +- Each completed child payload is plain JSON appended or updated in that array when the enumerator finishes the nested **child form**. +- **Add / Edit** opens the child form through the Formulus API [`openFormplayer`](../reference/formulus.md) with `{ subObservationMode: true }`. The nested session returns child JSON **without** creating a separate synced observation for each completion. +- **Remove** deletes one embedded payload from the parent array only (within the current parent draft or saved observation). + +### Schema configuration + +| Property | Required | Description | +|----------|----------|-------------| +| `format` | yes | Must be `"sub-observation"`. | +| `linkedForm` | yes | Child **form type** opened for add/edit (non-empty string). | +| `parentKey` | yes | Field name written on child payloads linking back to the parent (for example a foreign key into the parent entity id). | +| `parentValuePath` | recommended | Dot path into **current parent form data** for that key’s value (falls back to parent `observationId` when absent). | +| `columns` | optional | `{ key, label }[]` entries for the on-screen summary list; if omitted, `displayField` drives a single summary column. | +| `displayField` | optional | Fallback field key for the summary column (default `observationId`). | +| `orderBy` | optional | Sort embedded items by field: string field name or `{ key, direction }` (`asc` / `desc`). Without `key`, sorts by `createdAt` descending when present on payloads. | +| `allowDelete` | optional | Default `true`. | +| `subObservationInitValues` | optional | Map merged into **initial params** when **adding** a new embedded child. Values support templates `{{parentValue}}`, `{{currentInstanceId}}`, or `{{dot.path}}` into parent data. | +| `subObservationEditInitValues` | optional | Map merged **on top of** the saved child payload when **opening an existing** embedded item for edit—useful when parent-derived fields must be refreshed each time (often omitted). | + +Example property on the parent schema: + +```json +{ + "linked_visits": { + "type": "array", + "format": "sub-observation", + "title": "Visits", + "linkedForm": "visit", + "parentKey": "household_id", + "parentValuePath": "hh_id", + "displayField": "visit_date", + "allowDelete": true, + "subObservationInitValues": { + "household_id": "{{parentValue}}" + } + } +} +``` + +:::note Types in legacy forms +Some forms use `type: ["array", "string"]` with `"format": "sub-observation"` for migration compatibility; Formplayer activates the control whenever `format` matches. +::: + +See also [Form specifications](../reference/form-specifications.md) and [Formplayer](../reference/formplayer.md). + ## Quick Start ### Creating a Custom Question Type diff --git a/docs/guides/form-design.md b/docs/guides/form-design.md index 0672c88..1b798e4 100644 --- a/docs/guides/form-design.md +++ b/docs/guides/form-design.md @@ -506,6 +506,12 @@ ODE supports various question types through the Formplayer component. Question t } ``` +### Sub-observations + +Use **`format: sub-observation`** on an array property for **embedded repeats**: each nested completion stores JSON on the parent observation. Adding or editing opens the linked child form in **sub-observation mode**, so Synkronus still receives **one** parent observation payload. + +See [Custom Extensions](./custom-extensions.md#sub-observations-format-sub-observation) for schema keys (`linkedForm`, `parentKey`, `parentValuePath`, `subObservationInitValues`, templates, etc.). + ## Working with Media & Special Field Types ODE Formplayer supports specialized field types for capturing media, location, signatures, and scanning codes. Each type has specific schema requirements and platform constraints. diff --git a/docs/reference/form-specifications.md b/docs/reference/form-specifications.md index e7acf9f..5261159 100644 --- a/docs/reference/form-specifications.md +++ b/docs/reference/form-specifications.md @@ -195,6 +195,29 @@ Question types are specified using the `format` property in the schema: } ``` +### Sub-observations (embedded observations of another form type) + +Collect **multiple related observations as JSON objects inside one parent observation** using `"format": "sub-observation"`. Each embedded item is edited in a nested Formplayer session (`openFormplayer` with `subObservationMode`). + +See the full schema options and examples in [Custom Extensions](../guides/custom-extensions.md#sub-observations-format-sub-observation). + +```json +{ + "linked_children": { + "type": "array", + "format": "sub-observation", + "title": "Related entries", + "linkedForm": "child_form", + "parentKey": "parent_id", + "parentValuePath": "parent_id", + "displayField": "name", + "subObservationInitValues": { + "parent_id": "{{parentValue}}" + } + } +} +``` + ### Multimedia Types #### Photo diff --git a/docs/reference/formplayer.md b/docs/reference/formplayer.md index ab13282..66c7c42 100644 --- a/docs/reference/formplayer.md +++ b/docs/reference/formplayer.md @@ -287,6 +287,7 @@ Formplayer supports various question types through custom renderers: - **GPS**: Location coordinates capture - **Signature**: Digital signature pad - **QR Code**: Barcode/QR code scanner +- **Sub-observations**: Embedded repeats — nested child forms whose payloads are stored as JSON inside a parent array property (see [Custom Extensions](../guides/custom-extensions.md#sub-observations-format-sub-observation)) ## Form Rendering @@ -439,7 +440,7 @@ Formplayer includes core question type renderers: - `AudioQuestionRenderer`: Voice recording - `VideoQuestionRenderer`: Video recording - `FileQuestionRenderer`: File attachment -- `QrcodeQuestionRenderer`: QR code scanner +- `SubObservationQuestionRenderer`: Embedded sub-observation repeats (`format: sub-observation`) — nested `openFormplayer` with `subObservationMode` ### Custom Renderers