The data model

A BWEB page is not a file. There is no .html or .vue to open, and looking for a page's file is the first wrong turn everyone takes when they arrive on BWEB. A page is a set of records arranged as a tree in the 4D database. The rendering engine walks that tree, the dev panel edits those records, the API writes them. Nothing else is involved.

This is the most structural idea in the component: everything you will meet later — the panel, events, templates, publishing to production — works on those same records.

Three levels: domain, page, block

A BWEB site is always organised the same way:

  • The domain (BSPK_WEB_DOMAIN): one site. Its host name, its languages, its general settings. A single 4D database can serve several — the one serving this documentation does.
  • The page (BSPK_WEB_DOMAIN_MENU): a menu entry and one URL per language. It also carries publication, access rights and SEO.
  • The block (BSPK_WEB_CONTENT): one element of the page — a container, a text, an input field, a listbox. Blocks nest inside one another.

The page you are reading is therefore a row in BSPK_WEB_DOMAIN_MENU, and the text you are reading is a text block in BSPK_WEB_CONTENT, sitting inside a container block that gives it its margins.

A block, and its fields

A block is a handful of fields. Knowing them saves you from looking elsewhere for something that is necessarily there.

Field Role
uuidKeyThe primary key. Events and block selectors refer to it.
typeThe block type: wrapper, text, listbox, fieldText… there are 28 of them.
blockNameThe name you address the block by, from 4D and from JavaScript — to reload it, for instance.
webDomainMenuUuidThe page this block belongs to.
parentUuidThe direct parent block. Empty for a top-level block.
parentCollectionThe full ancestor path, space separated.
sortOrderPosition among siblings.
propertiesThe single bag holding the block's settings.
cssPropertiesStyling, filed by target, breakpoint and variant.
eventsThe actions triggered by the block's events.

Four audit fields complete the list — createdOn, createdBy, modifiedOn, modifiedBy — telling you who touched the block and when.

The tree: two fields, two jobs

parentUuid gives the direct parent. It is enough to go up one level, and it is what makes the nesting.

parentCollection gives the whole ancestor path, oldest first, separated by spaces. A block three levels down therefore holds something like "grandparent-uuid parent-uuid".

Why store both? To fetch an entire subtree in a single query instead of descending level by level. And for that query to be fast, it is written with the % operator:

ds.BSPK_WEB_CONTENT.query("parentCollection % :1"; $rootUuid)

% is 4D's word operator. Because the values are space separated, it compares a whole uuid, exactly, and it was measured around 200 times faster than the wildcard form on a 30,576-row bench. The = "@uuid@" form also works, but it scans the whole table.

Worth knowing: parentCollection is a Text field, and 4D refuses to index it. So there is no index to add — writing the query properly is the answer.

Trap: if parentCollection is wrong, the block vanishes from the page with no error at all: the tree walk simply never reaches it. When creating blocks programmatically, let the server compute that field rather than writing it yourself.

Ordering blocks uses decimals

sortOrder is a real, not an integer. To insert a block between positions 3 and 4, write 3.5: nothing after it needs renumbering. A small detail, but it changes how you reorganise a page programmatically.

What V21 removed

Up to V20, a block's settings were spread over four objects: objectProperties, blockProperties, htmlProperties and constraints. The boundary between them meant very little — the same property ended up in two different bags depending on which code had written it.

V21 merged them into one field, properties. The four old fields no longer exist in the table.

Before (V20) Today (V21)
objectPropertiesproperties
blockPropertiesproperties
htmlPropertiesproperties, with the htmlAttr flag in the schema
constraintsproperties, with the securityConstraint flag
vt_classNameElement_<target>cssProperties, under the matching target

Trap: a V20 snippet writing into objectProperties writes nowhere, and reading from it returns not an error but undefined. This is the most common way to lose an afternoon to outdated documentation. On import, old content is merged into properties on the fly; on export, only the V21 shape is written.

What comes next

The properties field does not accept just anything: each block type declares what it expects, and validation refuses the rest. That is the subject of The properties contract. For styling, see Styling: the CSS cube and Tailwind.