resolve-forward-references · git:20260821.ff81a24 · 2026-08-21 · sha256 e5a6d80213cf9f98
resolve-forward-references git:20260821.ff81a24A
Immutable. This exact content is served forever at /api/v1/blob/e5a6d80213cf9f98.
---
name: resolve-forward-references
description: "Order MDL statements so that every reference resolves — execution is sequential and immediate, so a document must exist before anything points at it. Use when a script fails on a reference to something defined later in the same file."
---
# Resolving Forward References in MDL Scripts
## Why Forward References Fail
MDL script execution is **sequential and immediate** — each `CREATE` statement commits
its document to the project database before the next statement runs. When a document is
being built, all its references (snippets, pages, microflows) are resolved against the
database at that moment. A reference to something defined *later in the same script* fails
because it is not in the database yet.
```
Error: snippet not found: MyModule.NavMenu
```
This applies to the following reference types:
| Reference | In | Fails when |
|---|---|---|
| `snippetcall` | page / snippet | snippet created after the page |
| `show_page` in action | page / snippet | page created after the page that references it |
| `SHOW PAGE` | microflow | page created after the microflow |
| `call microflow` | microflow | callee microflow created after the caller (in the same script, `exec` resolves the call against the project/backend, not later same-script definitions — so order the callee first) |
> **Note:** `SHOW PAGE` inside a microflow body resolves the page reference at
> microflow-creation time, not at invocation time. If the target page doesn't exist yet,
> the microflow creation fails.
---
## The Placeholder Pattern
The standard workaround is a three-step sequence:
1. **Create a minimal placeholder** for the document that will be referenced.
2. **Create all documents that reference it.** They bind to the placeholder's ID.
3. **Fill in the placeholder** using `CREATE OR MODIFY` or `ALTER` — both preserve the
original ID so existing bindings remain valid.
> **Critical:** Never use `CREATE OR REPLACE` for the fill-in step. `OR REPLACE` deletes
> the placeholder and creates a new document with a different ID. Every page or snippet
> that references the placeholder immediately becomes a dangling reference.
---
## Pattern 1 — Shared Navigation Snippet (most common)
A navigation snippet contains `show_page` buttons (references pages) and pages include
the snippet via `snippetcall` (references the snippet). Both sides reference each other.
```sql
-- Step 1: placeholder snippet (minimal valid content)
create snippet MyModule.NavMenu
(
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row r { column c (desktopwidth: 12) {
dynamictext loading (content: 'Loading...')
}}}
}
/
-- Step 2: pages that embed the snippet (snippet already exists → resolves OK)
create page MyModule.Customer_Overview
(
title: 'Customers',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row r {
column c1 (desktopwidth: 3) {
snippetcall nav (snippet: MyModule.NavMenu)
}
column c2 (desktopwidth: 9) {
datagrid dg (datasource: database MyModule.Customer) { }
}
}}
}
/
create page MyModule.Order_Overview
(
title: 'Orders',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row r {
column c1 (desktopwidth: 3) {
snippetcall nav (snippet: MyModule.NavMenu)
}
column c2 (desktopwidth: 9) {
datagrid dg (datasource: database MyModule.Order) { }
}
}}
}
/
-- Step 3: fill in the snippet with real content (pages now exist → show_page resolves OK)
-- Use CREATE OR MODIFY (preserves ID) or ALTER SNIPPET (in-place)
create or modify snippet MyModule.NavMenu
(
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row r { column c (desktopwidth: 12) {
actionbutton btnCustomers (
caption: 'Customers',
action: show_page MyModule.Customer_Overview
)
actionbutton btnOrders (
caption: 'Orders',
action: show_page MyModule.Order_Overview
)
}}}
}
/
```
---
## Pattern 2 — Page References Another Page (new/edit from overview)
An overview page has a New button that opens a NewEdit page via `show_page`. The NewEdit
page must exist before the overview can reference it.
```sql
-- Solution: declare the target page first (even if empty), then the referencing page
create page MyModule.Customer_NewEdit
(
params: { $Customer: MyModule.Customer },
title: 'Edit Customer',
layout: Atlas_Core.PopupLayout
)
{
layoutgrid g { row r { column c (desktopwidth: 12) {
dataview dv (datasource: $Customer) {
textbox txtName (label: 'Name', attribute: Name)
}
actionbutton btnSave (caption: 'Save', action: save_changes)
actionbutton btnCancel (caption: 'Cancel', action: cancel_changes)
}}}
}
/
-- Now the overview can safely reference the NewEdit page
create page MyModule.Customer_Overview
(
title: 'Customers',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row r { column c (desktopwidth: 12) {
actionbutton btnNew (
caption: 'New',
action: microflow MyModule.ACT_Customer_New
)
datagrid dg (datasource: database MyModule.Customer) {
column colName (caption: 'Name', attribute: Name)
}
}}}
}
/
```
For simple cases, reordering declarations is sufficient and no placeholder is needed.
---
## Pattern 3 — Microflow References a Page Not Yet Created
```sql
-- If the page is defined later in the script, create a placeholder or reorder.
-- Easiest fix: declare the page before the microflow that shows it.
-- Page first
create page MyModule.Order_Detail
(
params: { $Order: MyModule.Order },
title: 'Order Detail',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row r { column c (desktopwidth: 12) {
dataview dv (datasource: $Order) {
textbox txtID (label: 'Order ID', attribute: OrderID)
}
}}}
}
/
-- Microflow after the page it references
create microflow MyModule.ACT_OpenOrder ($Order: MyModule.Order)
begin
@position(200,200)
show page MyModule.Order_Detail ($Order = $Order);
@position(400,200) return;
end;
/
```
---
## Ordering Rules for Dependency-Free Scripts
To avoid forward references entirely, follow this declaration order within a script:
```
1. Entities and associations (no cross-document references)
2. Enumerations and constants (no cross-document references)
3. Snippets (placeholder if needed)
4. Pages (reference snippets + other pages)
5. Snippets (fill-in step, if placeholder was used)
6. Microflows and nanoflows (reference pages, entities)
7. Navigation (references pages)
```
When generating MDL scripts, write sections in this order. Doing so avoids the placeholder
pattern for the majority of scripts.
---
## Choosing Between CREATE OR MODIFY and ALTER SNIPPET
Both preserve the snippet's ID. Use whichever fits:
| Approach | When to use |
|---|---|
| `create or modify snippet` | Rewriting the whole snippet body from scratch |
| `alter snippet` | Inserting or replacing specific widgets within an existing layout |
```sql
-- ALTER SNIPPET: targeted widget replacement (keeps surrounding structure)
alter snippet MyModule.NavMenu
replace activity loading
with actionbutton btnCustomers (
caption: 'Customers',
action: show_page MyModule.Customer_Overview
);
```
---
## Script Template for a Full CRUD Module
```sql
-- ============================================================
-- MyModule CRUD scaffold
-- Correct declaration order: snippets → pages → microflows → nav
-- ============================================================
-- 1. Placeholder for shared navigation (will reference pages created below)
create snippet MyModule.AppNav
(layout: Atlas_Core.Atlas_Default)
{
layoutgrid g { row r { column c (desktopwidth: 12) {
dynamictext placeholder (content: '...')
}}}
}
/
-- 2. NewEdit page (referenced by Overview's New button)
create page MyModule.Customer_NewEdit
(
params: { $Customer: MyModule.Customer },
title: 'Edit Customer',
layout: Atlas_Core.PopupLayout
)
{
-- ... widgets ...
}
/
-- 3. Overview page (references NewEdit + NavMenu)
create page MyModule.Customer_Overview
(
title: 'Customers',
layout: Atlas_Core.Atlas_Default
)
{
layoutgrid g { row r {
column c1 (desktopwidth: 3) {
snippetcall nav (snippet: MyModule.AppNav)
}
column c2 (desktopwidth: 9) {
-- ... datagrid with New button calling ACT_Customer_New ...
}
}}
}
/
-- 4. Fill in navigation (pages now exist)
create or modify snippet MyModule.AppNav
(layout: Atlas_Core.Atlas_Default)
{
layoutgrid g { row r { column c (desktopwidth: 12) {
actionbutton btnCustomers (
caption: 'Customers',
action: show_page MyModule.Customer_Overview
)
}}}
}
/
-- 5. Microflows (pages already exist)
create microflow MyModule.ACT_Customer_New ()
begin
@position(200,200)
$c = create MyModule.Customer;
@position(400,200)
show page MyModule.Customer_NewEdit ($Customer = $c);
@position(600,200) return;
end;
/
-- 6. Navigation (pages already exist)
alter navigation Responsive
insert menu item 'Customers'
action show_page MyModule.Customer_Overview;
```
---
## Related Skills
- [Create Page](../create-page/SKILL.md) — Full page syntax reference
- [Overview Pages](../overview-pages/SKILL.md) — Overview + NewEdit page patterns
- [ALTER PAGE/SNIPPET](../alter-page/SKILL.md) — In-place snippet modification