Adding a block type

A block type exists in four places. Miss one and the failure is silent, in a different way each time — which is what makes this expensive to discover by trial and error.

#PlaceWhat it decidesFailure if missing
1Resources/bweb/schema/blocks/<type>.jsonProperties, sections, events, validationresolve() returns {error: "Schema introuvable pour le type: <type>"}, so the panel has no field and validate() refuses every write
2Resources/bweb/bspk/<type>.htmlThe HTML actually renderedThe block renders as an empty <bspk>
3BSPK_SET_STORAGE_WEBPARAM (vc_DragableObject)Presence in the palette, icon, category, tree iconNobody can drop it — and see the security note below
4Misc/lang/<lang>/BSPK.jsonPalette label, section and field labels, help textsRaw keys (BSPK_myBlock) on screen

Then run BSPK_REFRESH_STORAGE. Nothing above is read from disk at render time.

1. The schema

SchemaEngine.resolve(<type>) merges, in this order: the fragments listed in includes (from schema/shared.json), the css fragment (schema/css.json, which also injects stylePresets), then the type's own properties — the last one wins. The declaration format of a single property is covered in The properties contract.

meta carries category, label, icon, draggable, internal, childrenAllowed and render. All 28 current types use render: "template"; no other value is implemented.

meta.category is not what the palette shows. The schemas use three values (container, form, others); the palette uses six, from a different list (point 3).

2. The template

BSPK_LoadTpl reads every file of Resources/bweb/bspk/ into Storage.vo_Tpl.bspk, keyed by Lowercase(File.name) — extension dropped. The renderer looks the template up by Lowercase($WebContent.type) (in GG_PROCESS_COMPONENTS).

So the file name is the type, case-insensitively. qrCodeScanner.html is found as qrcodescanner; slider-part1.html matches no type and is dead weight.

The template is then run through PROCESS 4D TAGS with three parameters:

In the templateIs
$1$WebContent.uuidKey — the block's uuid, for unique ids
$2The BSPK_WEB_CONTENT entity (a copy unless updateFromObject is set)
$3The renderer's ad-hoc object — where a type can hand itself extra values (the captcha puts vt_CaptchaText there)

Every template opens with the same preamble: take $2.properties when $2 is an object, otherwise BASE64-decode and parse $2. That second branch is how one template calls another directly — captcha.html renders a fieldText that way.

A render error is caught and turned into a toast naming the block, the type and the uuid, so a broken template does not take the page down. See 4D in BWEB.

3. The palette, and why it is not cosmetic

BSPK_SET_STORAGE_WEBPARAM pushes one object per type into Storage.vo_WebParam.vc_DragableObject:

{ "Xliff": "BSPK_myBlock", "dataItem": "myBlock", "icon": "bi-...",
  "category": "form", "children": 0 }

Six categories: container, content, form, data, navigation, and model (the slot zone, filtered out unless GG_IS_MODEL_EDITOR, in dev-pannel.html).

Three constraints apply to that list:

  • Entries of one category must stay contiguous: dev-pannel.html opens a new grid when the category changes and closes the last one by index.
  • The string container must not change: legacy-port.js uses it to know which blocks accept children.
  • The icon is reused by the block tree (GG_MODEL_TREE_NODE, navItem.html, navGroup.html), so a type absent from the palette has no icon there either.

The security consequence. GG_WEB_SOCKET_SECURITY_CHECK derives the list of input block types from this very collection:

vc_DragableObject.query("(category = form AND dataItem # button) OR (dataItem = listbox)")

Trap: a new input block filed under any other category is therefore not treated as a field by the POST check — its value is stripped from vt_FieldValue with no message. That is why fieldCapture and qrCodeScanner sit in form although the schemas file them under others.

4. Translations

The palette label is the Xliff key; the rest come from the schema (meta.label, section labels, each property's label and explanatoryText). All of them are keys resolved by BSPK_Translate, which returns the key itself when it is missing — so a forgotten key shows as BSPK_myBlock rather than raising an error. Add it to every language file; the editing rules are in Translations (never use JSON.stringify on those files).

5. Refresh, in this order

Schemas and templates live in Storage, populated by BSPK_LoadTpl and refreshed by BSPK_REFRESH_STORAGE. They are no longer read from disk on every resolve().

  • Changed a schema or a template: BSPK_REFRESH_STORAGE is enough.
  • Changed a host .4dm: RELOAD PROJECT, then BSPK_REFRESH_STORAGE.
  • Changed the component (BSPK_SET_STORAGE_WEBPARAM included): restart 4D.

The order matters when both changed: refreshing first rescans the class still in memory. See Reload or restart.

Trap: a malformed schema JSON is swallowed twice — once by the Try in BSPK_LoadTpl, once by the Try in SchemaEngine._loadJson. The type then resolves to Null and the panel is simply empty. Validate the JSON before blaming the panel.

What creating a block does at runtime

Dropping from the palette posts addItem: "<type>" to WebFormController.moduleSave. It names the block with getUniqueBlockName, calls GG_INIT_CSS_PROPERTIES, then applies per-type CSS defaults for accordion, slider and wrapper. Nothing else is initialised: a new type starts with empty properties, so every template must tolerate an absent key (see The 4D language).

See also