Guides

Data views: a query shown as table, list or board

The dataView Block type: its stored query and layout, the three layouts and what a board needs, how the view asks and refreshes, /ansicht, and the Typed blocks table as the table layout.

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:

KeyTypeMeaning
querytext, hiddena DataQuery as JSON text; the properties form never shows it
layoutselecttable (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.

LayoutShowsRequires
tableone row per Block: title, Block type, then one column per property (long text such as a transcript and hidden properties get none)nothing
listthe title first, then the short properties as fieldsnothing
boardone lane per option of the group field, a card per Blockgroup

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, with evaluateQuery; the line below the view then says „Nur die Blöcke dieses Dokuments“.
  • The state is one of no-query, loading, ready, needs-group and error (a refused query lists its QueryIssues). 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 every FOREIGN_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 query or layout asks 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, $doc and $basket (the baskets are its choices). The operators are the ops of the field's kind in PROPERTY_KINDS, so a date offers before/after/on, a number lt/gte, a status or select its options as choices, a checkbox yes/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.

Copyright © 2026