Data views: a query shown as table, list or board
A data view is a Typed block of the Block type dataView that shows the answer to a query as a table, a list or a board. It stores the query and the layout and nothing else: the rows are never part of the Document, they are asked for whenever the view is on screen. Copy the Document, export it, or let a collaborator open it: the view shows what the query finds at that moment.
defaultBlockTypes(t) lists the type (label „Ansicht“ / "View", icon i-lucide-table-properties, capabilities properties and the colour basket), so a host that spreads the built-in list has it.
What a data view stores
Two Block properties in the Typed block's meta:
| Key | Type | Meaning |
|---|---|---|
query | text, hidden | a DataQuery as JSON text; the properties form never shows it |
layout | select | table (the default), list or board |
import { dataViewLayoutOf, dataViewQueryOf, newDataViewMeta } from '@skaile/forge-editor/composites'
newDataViewMeta('task') // { query: '{"v":1,"source":{"kind":"blocks","blockType":"task"}}' }
dataViewQueryOf(node) // DataQuery | null; null for no query, bad JSON or a query that names no Block type
dataViewLayoutOf(node) // 'table' | 'list' | 'board'; an unknown value reads as 'table'
dataViewQueryOf reads the JSON text like collectionQueryOf does and checks only its shape; validateQuery names the issues against the Block type's fields, and a view whose query is wrong shows them instead of rows.
The three layouts
A layout turns the DataResult into the model a view draws (VIEW_LAYOUTS, features/composites/views/layouts.ts). Cell text, sort keys and the right-hand alignment of numbers come from the Property kind registry (PROPERTY_KINDS), so a new property type shows in every layout without code here.
| Layout | Shows | Requires |
|---|---|---|
table | one row per Block: title, Block type, then one column per property (long text such as a transcript and hidden properties get none) | nothing |
list | the title first, then the short properties as fields | nothing |
board | one lane per option of the group field, a card per Block | group |
A board needs a group in the query: its lanes are the options of the grouped property. For a status property the lane carries the option's group (todo, doing, done), for a select the options in their declared order; a lane of an option nobody uses stays on the board (a board shows where nothing is yet), the lane of Blocks without a value comes last, and group.hideEmpty drops it when it holds nothing. viewModelOf(layout, result, defs, env, input) refuses a query without a group for the board ({ ok: false, missing: ['group'] }), and the view then says so instead of drawing no lanes.
Choosing the board in the view (chooseDataViewLayout(editor, node, 'board')) on a query without a group writes the status property (else the first status or select property of the queried Block types) as the group, so the board shows lanes at once. Going back to the table keeps the group.
How a view asks
The model is features/composites/views/data-view-model.ts (createDataViewModel), shared by DataView.vue and DataView.tsx: one model, two thin twins.
- It answers with
backend.index.query(the Blocks of every Document the user may read). A backend without it answers the same query locally over the Blocks of the open Document, withevaluateQuery; the line below the view then says „Nur die Blöcke dieses Dokuments“. - The state is one of
no-query,loading,ready,needs-groupanderror(a refused query lists itsQueryIssues). The previous rows stay on screen while the next answer is on its way, and an answer that equals the one shown does not redraw the view. - Only a view on screen asks. The node view reports its visibility (
model.setVisible); a visible view listens for changes of the Blocks (its own Document and the Blocks of other Documents, as the reference chips do), asks again 200 ms after the last change (DATA_VIEW_DEBOUNCE_MS), and everyFOREIGN_CONTENT_REFRESH_MS(the Blocks of other Documents change without the editor hearing of it). A view scrolled away stops all three and asks again when it comes back. - A change of the block's
queryorlayoutasks at once. - Row clicks reveal the Block in the open Document, or open it in its own Document through the host's
openBlock(revealCollectionItem).
Node view
Both bindings supply the node view (CompositeNodeViews.dataView): the usual Typed block view with the answer below its content, and layout buttons while the editor is editable. A binding that renders no live content leaves dataView out of nodeViews; the block then renders as any Typed block and its query stays in meta.query. Both bindings pass typedBlockViewOptions to the renderer, like collection:
compositeNodes({
typedBlock: VueNodeViewRenderer(CompositeView, typedBlockViewOptions),
dataView: VueNodeViewRenderer(DataView, typedBlockViewOptions),
reference: VueNodeViewRenderer(ReferenceView),
})
The query builder
In an editable editor the layout bar of a data view has an "Abfrage bearbeiten" button that opens the query builder (QueryBuilder.vue / QueryBuilder.tsx, both over createQueryBuilderModel in views/query-builder-model.ts; createEditorQueryBuilder(editor, node) wires it to a view). The form has:
- the Block type the query reads (a stored query that names several types keeps them until another type is chosen);
- a flat AND list of conditions, each
field,operator,value. The fields are the properties of the type plus the pseudo-fields$title,$tags,$type,$id,$docand$basket(the baskets are its choices). The operators are theopsof the field's kind inPROPERTY_KINDS, so a date offersbefore/after/on, a numberlt/gte, astatusorselectits options as choices, acheckboxyes/no. Changing the field resets the operator to the first one of its kind; - one sort field with a direction and one group field (with "hide the group without a value").
Every change builds the query again and runs validateQuery. Only a complete, valid query is written to the block's meta.query (through setDataViewQuery); a row without a value yet, or a query the validator refuses, writes nothing and the form says how many parts are open. The view then asks again at once because its node changed. What the form has no control for stays as stored: search, aggregates, select, page, the scope, sorts after the first, and a filter with or or nesting (the form shows it as kept and offers to replace it). A change of the stored query from outside (another user, an undo) is followed by the open form.
import { createEditorQueryBuilder } from '@skaile/forge-editor/composites'
const builder = createEditorQueryBuilder(editor, node) // null without a block ID or a translator
builder?.setBlockType('task')
const id = builder!.addCondition()
builder!.setConditionField(id, 'status')
builder!.setConditionValue(id, 'done') // writes `status eq done`
builder!.setGroup('$basket')
/ansicht
The slash command /ansicht inserts a data view after the caret's block over the Block type that has the most Typed blocks in the open Document (the first one met wins a tie; a data view never counts). A Document without another Typed block gets a view over the default Block type of the settings. insertDataView(editor, fallbackType) does the same from code, and insertComposite(kind, meta) takes the meta of the block it creates.
The Block type's own slash entry (/dataView) is not offered: an empty view says nothing, /ansicht always starts with a query.
The Typed blocks table is the table layout
CompositeTable (Vue and React) lists the Typed blocks of the open Document. It does not have a table of its own: its columns are those of VIEW_LAYOUTS.table (tableColumnsOf), its filters are one DataQuery that evaluateQuery answers over rowsAsBlockRows(rows), and cell text and sort keys come from the kind registry. tableViewOf adds what only the panel has (tags, summary, search box, the sort the header sets). A column or kind added to the layout shows in the panel and in every data view.
Collections: an ordered list of Blocks
The collection Block type: its items and stored query, the node view with reorder and „Aus Abfrage füllen“, the block action that adds a Block, and the Markdown export.
Explore: backlinks, related Blocks and the trail
What is connected to a Block: the Blocks that point at it, the Blocks related to it, and the chain of Derivations behind it. The explore Feature, the panel, the related route and derivationTrailOf.