Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/guides/building-custom-apps-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/building-custom-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
56 changes: 56 additions & 0 deletions docs/guides/custom-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions docs/guides/form-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 23 additions & 0 deletions docs/reference/form-specifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/formplayer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
Loading