Data Views
Render content from WCL data: wdoccomponent / slot / repeater / instance, body & project, partial & collect._
A *data view* renders document content — cards, tables, charts, diagrams — from a WCL data structure rather than hand-authored blocks. Declare the data once, then derive every view from it. The primary tool is a component: a reusable fragment of ordinary wdoc markup with named slots.
Components
Declare a wdoc_component with wdoc_slots and a wdoc_body of ordinary markup. Reference slots in any $"…${slot}…" interpolated string or as a bare identifier in a field (class = [status]). A slot with a default is optional. Instantiate the component by its own name.
wdoc_component dv_metric {
wdoc_slot label
wdoc_slot value
wdoc_slot status { default = "note" }
wdoc_body {
callout $"" { class = [status] body = $"Currently at **%**" }
}
}
// ... then, anywhere a block is allowed:
dv_metric { label = "CPU" value = 42 status = "warning" }
dv_metric { label = "Memory" value = 88 } // status defaults to "note"
Interpolating slots
Slot values land in text via WCL's $"…" interpolated strings — note the $ prefix. A plain "…" string is literal. Bare references in a field (like class = [status]) need no prefix.
Repeating over data
wdoc_repeater renders its body once per element of each, binding the element to the symbol named by as. Combined with a component it stamps one card per data row; with no component its body is just markup with the loop variable in scope. A slot can hold a whole list, and a repeater inside a component body can iterate it.
wdoc_repeater { each = inventory as = :h
dv_metric { label = h.name value = h.cpu status = h.status }
}
Generating pages and navigation
A wdoc_repeater is the single iteration concept at every level. At the document root, give it a page block and it emits one rendered page per element — the page's interpolated label becomes the route. Inside a toc (or a chapter), give it a chapter block and it emits one navigation entry per element.
wdoc_repeater { each = containers as = :c
page $"cont_" {
sites = [:docbook]
title = c.name
h1 $""
}
}
site docbook {
default_template = :book
toc {
chapter "Containers" {
wdoc_repeater { each = containers as = :c
chapter $"" { page = $"cont_" }
}
}
}
}
Routes must be slug-safe and unique
A generated route is its interpolated label, so it must be non-empty, contain only A-Za-z0-9_-, and be unique within its site. Build a slug from prose with to_lower(replace(s, " ", "-")).
Render by reference
A wdoc_instance renders the component named by the value of its component field — so a repeater can emit a *different* component per element. The instance's like-named fields fill the target's slots (falling back to each slot's default).
wdoc_repeater { each = widgets as = :row
// `component` is data, so each element picks its own component.
wdoc_instance { component = row.kind label = row.label value = row.value status = row.status }
}
Content slots (layout wrappers)
A wdoc_content block in a component body marks where the instance's *own* nested blocks render — so a component can frame arbitrary content.
wdoc_component dv_panel {
wdoc_slot title
wdoc_body {
h3 $""
wdoc_content // the caller's nested blocks render here
}
}
dv_panel { title = "Notes"
p "Anything nested in the instance renders at wdoc_content."
list { li "including lists" li "and more" }
}
Partials (scatter and collect)
A partial tags a body of blocks; a collect with the same tag gathers every matching partial — across the whole document and its imported files — and renders their bodies, in document order, at the collect site. A partial is invisible where it's defined unless you set show_here = true. It's the appendix / glossary / collected-sidebars pattern.
// Scatter tagged deposits anywhere — different blocks, even imported files:
partial aside { callout "From section one" { body = "A point to collect later." } }
// ... prose, other blocks ...
partial aside { callout "From section two" { body = "Another point." } }
// Gather every `aside` partial here, in document order:
collect aside
Scope and limits
Collection is document-global: a collect gathers matching partials from the root document and every file pulled in by a top-level import. Partials in block-scoped (lazily imported) files aren't reached, and a collected body should avoid ids.
Content fragments on data (body and project)
A body attaches a chunk of renderable content to a data record as a *property* — without that record being a renderable block — and a project renders it elsewhere by reference. Declare your own block type with a @child("body") slot, author the content inside each record, then project it from a repeater. Because body is @by_ref, from = s.overview resolves to *that* record's fragment, and ${…} inside the body resolves against the record.
@block("server")
type Server {
@inline(0) name: identifier
region: utf8?
@child("body") overview: WdocAddressableBody? // content rides on the record
}
server web01 { region = "us-east"
body { p $"Frontend in ." } // NOT a renderable block here
}
page fleet {
wdoc_repeater { each = servers as = :s
h2 $""
project { from = s.overview } // render THIS record's body
}
}
Addressing
A single @child("body") slot is addressed by its slot, so the body needs no name. A body in a @children("body") list, or one declared at the document root, is addressed by its @inline(0) name. The record carrying a body may be nested (a step inside a tutorial). A body never renders where it's declared, only where projected.
Documenting schema types
The built-in type_table component documents a schema type by reflecting it — type_table { type = Image } renders a table of the type's properties (name, type, required, description), including inherited fields. Descriptions and visibility are authored on the schema with @doc("…") and @hidden. block_reference { type = MyDoc } walks a document's @child / @children slots and emits an h3 plus a type_table for each.
Examples
One card per data row
A wdoc_repeater renders its body once per element of each, binding the element to the symbol named by as. Combined with a component, it stamps one card per row of data.
wdoc_repeater { each = inventory as = :h
dv_metric { label = h.name value = h.cpu status = h.status }
}
Expected: One metric card per inventory entry, each reading its label, value, and status from the data row.
Related